Issuu on Google+

Programacion Web con WebBroker y

Internet ticne cada vcz un papel mas importantc cn el mundo, y gran parte de el depende dcl Cxito de la World Wide Web, la telaraiia mundial basada en el protocolo HTTP. En el capitulo anterior hemos comentado cl protocolo HTTP y el desarrollo de aplicaciones de cliente y de servidor basadas en el. Con la disponibilidad de varios servidores Web de alto rendimiento, escalables y flexibles, sera algo muy raro que se desee crear uno propio. Las aplicaciones Web dinarnicas se construyen en general integrando guiones o programas compilados con servidores Wcb, en lugar de sustituirlos por un software personalizado. Este capitulo se centra completamente en el desarrollo de aplicaciones de servidor, que amplien 10s servidores Web ya esistentes. Antes hemos comentado la generacion dinamica de paginas HTML. Ahora aprenderemos a integrar csta generacion dinimica con un servidor. Este capitulo supone la continuacion Iogica del anterior, pero con el no se completa la esplicacion acerca de la programacion orientada a Internet. El proximo capitulo se dedicara a la tecnologia IntraWeb disponible con Delphi 7, y el 22 vuelve a tratar la programacion para Internet desde el punto de vista de XML. -- -

-

-

..

---.

----

--

A D ~ ~ W & K I A :para probpr algunos de 10s ejemplos de este capitulo se n e c e s ~ a c c e s oa un m i d o r w&. L'B sqlpcibn mas sencilla es utilizar--la

I


En este capitulo se tratan 10s siguientes temas: Paginas Web dinamicas. CGI, ISAPI y modulos de Apache. La arquitectura de WebBroker. Web App Debugger. La arquitectura de WebSnap. Adaptadores y guiones de servidor.

Paginas Web dinarnicas Cuando se navega por un sitio Web, generalmente se descargan paginas estaticas (archivos de texto con formato HTML) desde el servidor Web hasta el ordenador cliente. Como desarrollador Web, se pueden crear manualmente estas paginas, pero para la mayoria de 10s negocios, tiene mas sentido crear las paginas estaticas a partir de la informacion de una base de datos (un servidor SQL, una serie de archivos, y cosas asi). Mediante este enfoque, basicamente se genera una instantanea de 10s datos en formato HTML, lo que resulta bastante razonable si 10s datos cambian muy a menudo. Este enfoque se comento en el capitulo anterior. Como alternativa a las paginas HTML estaticas, esiste la posibilidad de hacerlas dinamicas. Para ello, extraemos la informacion directamente desde una base de datos, en respuesta a la solicitud del navegador, para que el HTML enviado por la aplicacion nos muestre 10s datos actuales, no una vieja instantanea de 10s mismos. Este metodo resulta apropiado si 10s datos cambian con frecuencia. Como ya hemos mencionado, hay un par de maneras de programar el comportamiento personalizado en un servidor Web y estas son tecnicas ideales para generar paginas HTML dinamicamente. Ademas de las tecnicas basadas en guiones, que son muy populares, 10s dos protocolos mas comunes para programar servidores Web son CGI (Common Gateway Interface o Interfaz de pasarela comun) y las API de 10s servidores Web.


- - - - - - . - - - - - -- NOTA: Hay que tener presente que la tecnologia WebBroker de Delphi (disponible en las ediciones Enterprise Studio y Professional) reduce las diferencias entre CGI y las API de servidor a1 ofrecer un marco de trabajo de clases comun. De este modo, se puede convertir con facilidad una aplicacion CGI en una biblioteca ISAPI o integrarla con Apache. - ---- -

-

--

-

-

-

-

Un resumen de CGI CGI es un protocolo estandar de comunicaciones entre el navegador cliente y el servidor Web. No se trata de un protocolo particularmente eficaz, per0 se usa mucho y no es especifico de ninguna plataforma. Este protocolo permite a1 navegador tanto solicitar como enviar datos y se basa en la entradalsalida de linea de comandos estandar de una aplicacion (normalmente una aplicacion de consola). Cuando el servidor detecta la solicitud de una pagina para la aplicacion CGI, pone la aplicacion en marcha, pasa 10s datos de linea de comandos desde la solicitud de pagina a la aplicacion y despues envia la salida estandar de la aplicacion a1 ordenador cliente. Se pueden usar muchas herramientas y lenguajes para escribir aplicaciones CGI y, Delphi es solo una de ellas. A pesar de la limitacion obvia de que el servidor Web debe ser un sistema Windows o Linux basado en Intel, se pueden crear programas CGI bastante complicados en Delphi y Kylix. CGI es una tecnica de bajo nivel, ya que utiliza la entrada y salida estandar de la linea de comandos junto con variables de entorno para recibir information desde el servidor Web y enviarla de vuelta. Para crear un programa CGI sin utilizar clases de soporte, podemos generar simplemente una aplicacion de consola de Delphi, eliminar el codigo fuente tipico del proyecto y reemplazarlo por las instrucciones siguientes: program CgiDate; { S A P P T Y P E CONSOLE} u s e s SysUtils; begin

writeln writeln; writeln writeln writeln writeln writeln writeln writeln

( ' c o n t e n t - t y p e : t e x t / h t m l l ):

('<html><head>' ) ; ( ' <title>Time a t t h i s s i t e < / t i t l e > ' ); ( ' < / h e a d > < b o d y > ' ); ('<hl>Time a t t h i s s i t e < / h l > ' ) ; ( ' <hr>' ) ; ( ' <h3>' ) ; (FormatDateTime ( ' " T o d a y i s d d d d , mmmm d , y y y y , ' "'<br> a n d t h e t i m e i s r 1 hh:mrn:ss A M / P M 1 , Now) ) ; writeln ( ' < / h 3 > ' );

+


writeln writeln writeln end.

<hr>'); ('<i>Page generated by CgiDate.exe</i>'); ('</body></html>');

( I

Los programas CGI generan normalmente un encabezamiento que va seguido del texto HTML utilizando la salida estandar. Si ejecutamos directamente este programa, podremos ver el texto en una ventana de terminal. Si por el contrario, lo ejecutamos desde un servidor Web y enviamos la salida a un navegador, aparecera el texto HTML formateado como muestra la figura 20.1.

0--, -t4) Rhm

-

R y p a C

Ad

@ IU r

7

Mtp {lladhost/cg~-bnlcgsddeexe

lmo

--

-

yi

---

Time at this site Today is domingo, junio 15, 2003, and the time is 05:31 PRI -

-

---

-

-

. -

Page generated by CgiDate-axe

IS

Figura 20.1. La salida de la aplicacion CgiDate, vista en un navegador.

La creacion de aplicaciones avanzadas y complejas con CGI requiere mucho trabajo. Por ejemplo, para extraer informacion de estado sobre la solicitud HTTP, es necesario acceder a variables relevantes del entorno, tal y como sigue: / / obtiene el nombre de la ruta GetEnvironmentVariable ( ' P A T H - I N F O ' , (PathName)) ;

PathName, sizeof

Uso de bibliotecas dinarnicas Un metodo completamente distinto es la utilizacion de la API de un servidor Web, la popular ISAPI (Internet Sewer API, presentada por Microsoft) y la no tan conocida NSAPI (Netscape Server API), o la API de Apache. Estas API nos permiten escribir una biblioteca que el servidor carga en su propio espacio de direcciones y que mantiene algun tiempo en memoria. DespuCs de haber cargado la biblioteca, el servidor puede ejecutar solicitudes individuales mediante hebras


dentro del proceso principal, en lugar de lanzar un nuevo ejecutable para cada solicitud (corno sucede en las aplicaciones CGI). Cuando el servidor recibe una solicitud de pagina, carga la DLL (si no lo ha hecho ya) y ejecuta el codigo adecuado, que puede poner en marcha un nuevo hilo o thread, o utilizar uno existente para procesar la solicitud. La biblioteca envia entonces 10s datos HTTP correspondientes a1 cliente que ha solicitado la pagina. Dado que esta comunicacion se suele producir en memoria, este tipo de aplicacion es mucho mas rapida que el enfoque CGI.

Tecnologia WebBroker de Delphi El fragment0 de codigo CGI mostrado demuestra el enfoque simple y direct0 de este protocolo. Podriamos haber ofrecido ejemplos de bajo nivel similares para ISAPI o modulos de Apache, pero en Delphi es mas interesante utilizar la tecnologia WebBroker. Esta tecnologia implica una jerarquia de clases dentro de las bibliotecas VCL y CLX (creada para simplificar el desarrollo en el lado de servidor para la Web) y un tip0 de modulos de datos especifico llamado WebModules. Tanto las ediciones Enterprise Studio como Professional de Delphi incluyen este marco de trabajo (corno contrapartida del mas novedoso y avanzado entorno WebSnap, que solo esta disponible en la edicion Enterprise Studio). La tecnologia WebBroker de Delphi permite desarrollar con gran facilidad una aplicacion ISAPI o CGI, o un modulo de Apache. En la primera ficha (New) del cuadro de dialogo New Items, seleccionamos el icono W e b Server Application. El cuadro de dialogo siguiente ofrecera opciones como ISAPI, CGI, modulo de Apache 1 o Apache 2 y el Web App Debugger.

En cada caso, Delphi generara un proyecto con un WebModule, que es un contenedor no visual similar a un modulo de datos. Esta unidad sera identica, sin importar el tip0 de proyecto; so10 cambia el archivo principal del proyecto. Para una aplicacion CGI tendra este aspecto:


Este nombre de ruta es una parte de la URL de la aplicacion CGI o ISAPI, que viene detras del nombre del programa y antes de 10s parametros, como path1 en la siguiente URL:

Al proporcionar acciones diferentes, la aplicacion puede responder con facilidad a solicitudes con diferentes nombres de ruta y podemos asignar un componente productor distinto o llamar un controlador del evento OnAction diferente para cada nombre de ruta posible. Desde luego, podemos omitir el nombre de la ruta para manejar una solicitud generica. Tambien hay que considerar que en vez de basar la aplicacion en un WebModule, podemos utilizar un simple modulo de datos y aiiadirle un componente WebDispatcher. Es un buen metodo para convertir una aplicacion Delphi ya existente en una extension de servidor Web.

-

.- - - . - - - e bhsica WebDispatcher ate. Los programas de w. CUDIUKCI uu uucucu CCUCI V~LI ,US U C S U ~ L U ~ L U U I C S o- varios - . . . .. . .. . . .. - .- . .. .. -. .- mbdulos . .. - - Web. . . . Tambitn hay que tener en cuenta que todas las acciones del WebDispatcher no tienen nada que ver con las acciones almacenadas en un componente .

-

A

-

-

~ * lI

-

..

A

-

~

r

-. - I S - -

-

-

- -

-

-

- -

-7-

-- -

-

- -

--

-

-

---

-

-

-

-. .

Cuando definimos las paginas HTML adjuntas que ponen en marcha la aplicacion, 10s vinculos haran solicitudes de paginas a las URL para cada una de esas rutas. Tener una sola biblioteca que pueda llevar a cab0 diferentes operaciones en funcion de un parametro (en este caso el nombre de la ruta), permite que el servidor pueda guardar una copia de esta biblioteca en memoria y responder mucho mas rapidamente a las solicitudes del usuario. En parte, sucede lo mismo para una aplicacion CGI: el servidor tiene que ejecutar varias instancias per0 puede guardar en cache el archivo y hacerlo que este disponible mas rapidamente. En el evento OnAction es donde escribimos el codigo para especificar la respuesta a una consulta dada, 10s dos parametros principales pasados al controlador del evento. Veamos un ejemplo: procedure TWebModulel.WebModulelWebActionItemlAction(Sender: TObj ect; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean) ; begin Response.Content := ' <h tml><head><title>Hello Page</ti tle></head><body>' + ' <hl>Hello</hl>' + ' <hr><p><i>Page genera ted by Marco</i></p></body></html>' ; end;


En la propiedad C o n t e n t del parametro R e s p o n s e es donde tenemos que insertar el codigo HTML que queremos que vean 10s usuarios. El unico inconveniente de este codigo es que en un navegador la salida se mostrara correctamente en varias lineas, per0 si miramos el codigo fuente HTML, podremos ver una sola linea que corresponde a toda la cadena. Para que el codigo HTML sea mas legible, tenemos que insertar el codigo de caracter #13 (nueva linea), dividiendolo en multiples lineas (o aun mejor, el valor multiplataforma s L i n e B r e a k ) . Para permitir que otras acciones controlen esta solicitud, tenemos que definir el ultimo parametro, Hand l e d , como F a l s e . De otra forma, el valor predeterminado sera T r u e y una vez que hayamos controlado la solicitud con la accion, el WebModule asumira que hemos terminado. La mayor parte del codigo de una aplicacion Web se encuentra en 10s controladores del evento OnAct i o n para las funciones definidas en el contenedor WebModule. Estas funciones reciben una solicitud del cliente y devuelven una respuesta utilizando para ello 10s parametros Request y Response. Cuando se utilizan componentes productores, el evento O n A c t i o n suele devolver, como R e s p o n s e .C o n t e n t , el C o n t e n t del componente productor, con una simple operacion de asignacion. Se puede acceder directamente a este codigo asignando un componente productor a la propiedad P r o d u c e r de la accion, sin necesidad de tener que escribir nunca mas estos controladores de evento (pero no hay que hacer ambas cosas, pues podria traer problemas).

'

clases productoras personalizadas que+h&edartde la c b 7 ! c u s tomcontent Producer, sin0 queimplernentan la i n t e r f e 3Pr~duceContent. &a propiedad P r o d u c e r C o n t e n t es.casi una propiedad de interfaz que se comporta de la misma manera gracias orsu editor depropiedad y no esta basada en el soporte para propiedadeg-de i n t e d b de DeIphi 6.

L

Depuracion con Web App Debugger Depurar aplicaciones Web escritas en Delphi suele ser complicado. No basta con ejecutar el programa y puntos de ruptura, sino que hay que convencer a1 servidor Web para que ejecute el programa CGI o la biblioteca dentro del depurador de Delphi. Se puede hacer eso si se indica una aplicacion anfitrion en el cuadro de dialogo Run Parameters de Delphi, per0 este enfoque implica dejar que Delphi ejecute el servidor Web (que suele ser un servicio de Windows, no un programa independiente). Para resolver estas cuestiones, Borland ha desarrollado un prograrna de depuracion especifico, Web App Debugger. Este programa que se activa mediante el elemento correspondiente del menu Tools es un servidor Web que atiende a


pcticiones en un puerto que puede especificarse (de manera predefinida el 1024). Cuando llega una peticion, el programa puede redirigirla a un ejecutable independiente. En Delphi 6, esta comunicacion se basaba en las tecnicas de COM; en Delphi 7 se basa en sockets de Indy. En ambos casos, puede ejecutarse la aplicacion de servidor Web desde el IDE de Delphi, establecer todos 10s puntos de ruptura necesarios y, despues, (cuando el programa se active mediante el Web App Debugger) depurar el programa tal y como se haria con un sencillo archivo ejecutable. El Web App Debugger hace un buen trabajo con el registro de todas las peticiones recibidas y las respuestas devueltas a1 navegador. El programa tambien tiene una pagina Statistics que realiza el seguimiento del tiempo necesario para cada respuesta, con lo que se puede comprobar la eficacia de una aplicacion en distintas condiciones. Otra nueva caracteristica del Web App Debugger en Delphi 7 es que ahora es una aplicacion CLX en lugar de una aplicacion VCL. Este cambio en la interfaz de usuario y la conversion de COM a sockets se han llevado a cabo para que pueda utilizarse en Kylix.

ADVERTENCIA:Debido a que el Web App Debugger utilivr sockets de Indy, la aplicacion recibira frecuentes excepciones & tipo EidConnPor este motivo, esta ex~epcionse inhabilita automaticamente en todos 10s proyectos de Delphi 7. C 1o s edGr a ce fu 11y .

A1 utilizar la opcion correspondiente en el dialog0 New Web Server Application, se puede crear con facilidad una nueva aplicacion compatible con el depurador. Esta opcion define un proyecto estandar, que crea tanto un formulario principal como un modulo Web. El formulario (inutil) incluye codigo para proporcionar el codigo de inicializacion y aiiadir la aplicacion al Registro de Windows. initialization TWebAppSock0bjectFactory.Create ( 'proqrdmmndme1) ;

El Web App Debugger utiliza esta inforrnacion para conseguir una lista de 10s programas disponibles. Hace esto cuando se utiliza el URL predeterminado para el depurador, indicado en el formulario como un enlace, tal y como muestra, por ejemplo, la figura 20.2. La lista incluye todos 10s servidores registrados, no solo aquellos en ejecucion, y puede usarse para activar un programa. Sin embargo, no se trata de una buena idea porque hay que ejecutar el programa dentro del IDE de Delphi para poder depurarlo. (Fijese en que puede expandirse la lista a1 hacer clic sobre View Details; esta vista incluye una lista de 10s archivos ejecutables y muchos otros detalles.) El modulo de datos para este tipo de proyecto incluye codigo de inicializacion: uses W e b R e q ;

initialization


i f WebRequestHandler <> n i l then WebRequestHandler.WebModuleC1ass

: = TWebModule2;

Registered Servers View Llst 1 V ~ e wD e t d s G J o sewer~nfoSewerlnfo WSnapl WSnapl WSnap2 WSnap2 WSnapMD WSnapMD WSnapSess~onWSnapSession WSnapTable.WSnapTable

Figura 20.2. Se muestra una lista de aplicaciones registradas con el Web App Debugger cuando se conecta con su pagina principal.

El Web App Debugger deberia utilizarse solo para depuracion. Para desplegar la aplicacion, deberia utilizarse alguna de las otras opciones. Pueden crearse 10s archivos de proyecto para otro tip0 de programa servidor Web y aiiadir al proyecto el mismo modulo Web que a la aplicacion de depuracion. El proceso inverso es ligeramente mas complejo. Para depurar una aplicacion ya existente hay que crear un programa de este tipo, eliminar el modulo Web, aiiadir el ya existente y parchearlo aiiadiendole una linea para establecer la variable W e b M o d u l e C l a s s del W e b R e q u e s t H a n d l e r , como en el fragment0 de codigo anterior.

ADVERTENCIA: Aunque en la may& de los c m $epoch4adaptar un programa de una tecnologia Web a @m,no alemprs serh aqi. ?or ejemplo, en el ejempk, CustQueP (&l que ya hablaratwa), heboa t e d o q u e evitar la propiedad ScriptName de la pt;tici(m (ips fi&onsr, pare m programa CGI) y u t d h t e n so hggr propmbd knternb13.@l;$pt~ame. Existen otros dos elementos bastante interesantes involucrados en el uso de Web App Debugger. En primer lugar, se pueden probar 10s programas sin tener instalado un servidor Web y sin tener que ajustar su configuration. En otras palabras, no es necesario desplegar 10s programas para probarlos (se pueden probar inmediatamente). En segundo lugar, en vez de realizar un desarrollo rapi-


do de una aplicacion como CGI, se puede comenzar a experimentar inmediatamente con una arquitectura multihilo, sin tener que enfrentarse con la carga y descarga de bibliotecas (que suele implicar el apagado del servidor Web y posiblemente incluso del ordenador).

Creacion de un WebModule multiproposito Para demostrar lo facil que resulta, utilizando el soporte de Delphi, crear una aplicacion de servidor completamente funcional, hemos creado el ejemplo BrokDemo. Este ejemplo se ha creado utilizando la tecnologia Web App Debugger, per0 deberia ser relativamente facil compilarlo como un CGI o una biblioteca de servidor Web. Un elemento clave del ejemplo de WebBroker es la lista de acciones. Las acciones pueden administrarse mediante el editor Action o directamente en el Object Treeview. Las acciones tambien se visualizan en la pagina Designer del editor, por lo que podemos ver graficamente sus relaciones con 10s objetos de la base de datos. Si examinamos el codigo fuente, podremos ver que hemos dado un nombre especifico a cada accion. Tambien hemos dado nombres significativos a 10s controladores del evento OnAction.Por ejemplo, TimeAction tiene un nombre de metodo que es mas comprensible que el que genera Delphi automaticamente, WebModulelWebActionItemlAction. Cada funcion tiene un nombre de ruta diferente, una de ellas seiialada como predeterminada, que se ejecuta aunque no se especifique un nombre de ruta. La primera idea interesante de este programa es la utilizacion de dos componentes Pageproducer, usados para la parte inicial y final de cada pagina, PageHead y PageTail.Centralizar este codigo facilita su rnodificacion, sobre todo si esta basado en archivos HTML externos. El HTML producido por estos componentes se aiiade a1 principio y a1 final del HTML resultante en el controlador del evento OnAfterDispatch del modulo Web: procedure TWebModulel.WebModulelAfterDispatch(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean) ; begin Response.Content : = PageHead.Content + Response.Content + PageTail-Content; end;

Hemos aiiadido a1 final del proceso de generacion de la pagina, el codigo HTML inicial y final porque esto permite que 10s componentes produzcan el HTML como si lo estuvieran haciendo ellos todo. El hecho de empezar con HTML en el evento OnBeforeDispatch,significa que no podemos asignar de forma directa 10s componentes productores a las funciones, ya que el componente productor sobrescribiria la propiedad Content que ya hemos proporcionado en la respuesta.


El componente PageTail incluye una etiqueta personalizada para el nombre del guion, reemplazado por el siguiente codigo, el cual utiliza el objeto de solicitud actual disponible dentro del modulo Web: procedure TWebModulel.PageTailHTMLTag(Sender: TObject; Tag: TTag; const TagString: String; Tagparams: TStrings; var ReplaceText: String); begin if TagString = 'script' then ReplaceText : = Request.ScriptName; end ;

Este codigo se activa para expandir la etiqueta <#script > de la propiedad HTMLDoc del componente PageTail. El codigo de las acciones de hora y fecha es sencillo. Realmente, la parte interesante empieza con la ruta del Menu, que es la accion predeterminada. En su controlador del evento OnAct ion,la aplicacion utiliza un bucle for para crear una lista de las acciones disponibles (usando sus nombres sin las dos primeras letras, las cuales son siempre Wa, en este ejemplo), proporcionando un vinculo para cada una de ellas con un ancla (una etiqueta <a>): procedure TWebModulel.MenuAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); var I: Integer; begin Response.Content : = ' < h 3 > M e n u < / h 3 > < ~ 1 > ' # 1 3 ; for I : = 0 to A c t i o n s - C o u n t - 1 do Response .Content : = Response. Content + <li> <a href="' Request .ScriptName + Action[I] .PathInfo + ' "> + Copy (Action[I].Name, 3, 1000) + '</a>'#13; Response.Content : = Response.Content + '</ul>'; end;

+

Otra accion del ejemplo Bro kDemo . proporciona a 10s usuarios una lista de 10s parametros del sistema relacionados con la solicitud, algo que es bastante util para la depuracion. Tambien es instructivo aprender cuhnta informacion (y exactamente que informacion) transfiere el protocolo HTTP desde un navegador a un servidor Web y viceversa. Para generar esta lista, el programa busca el valor de cada propiedad de la clase TWebRequest, como muestra este fragment0 de codigo: procedure TWebModulel.StatusAction(Sender: TObject; Request: TWebRequest ; Response: TWebResponse; var Handled: Boolean) ; var I: Integer; begin


Response .Content : = ' <h3>Status</h3>'#13 + 'Method: ' + Request.Method + '<br>'#13 + ProtocolVersion: ' + Request.ProtocolVersion + '<br>'#13 + 'URL: ' + Request-URL + '<br>'#13 + 'Query: ' + Request.Query + '<br>'#13 + ...

lnformes dinamicos de base de datos El ejemplo BrokDemo define otras dos acciones mas, indicadas mediante 10s nombres de ruta / t a b l e y / r e c o r d . Para estas dos ultimas acciones, el programa genera una lista de nombres y luego presenta 10s detalles de un registro, utilizando un componente DataSetTableProducer para dar formato a toda la tabla y un componente DataSetPageProducer para crear una vista del registro. Veamos a continuacion las propiedades de estos dos componentes: object DataSetTableProducerl: TDataSetTableProducer DataSet = dataEmployee OnFormatCell = DataSetTableProducerlFormatCell end object DataSetPage: TDataSetPageProducer HTMLDoc.Strings = ( ' <h3>Employee : <#Las tName></h3> ' ' <ul><li> Employee ID: <#EmpNo> ' ' <li> Name: <#FirstName> <#LastName>' < l i > Phone: <#PhoneExt>' '<li> Hired On: <#HireDate>' ' <li> Salary: <#Salary></ul>' ) OnHTMLTag = PageTailHTMLTag DataSet = dataEmployee end

Para producir toda la tabla, simplemente conectamos el DataSetTableProducer a la propiedad P r o d u c e r de las acciones correspondientes sin proporcionar ningun controlador de evento especifico. La tabla se hace mas potente si aiiadimos vinculos internos a 10s registros especificos. El codigo siguiente se ejecuta para cada celda de la tabla, per0 solamente se crear un enlace para la primera columna a partir de la primera fila (no se incluye la celda del titulo): procedure TWebModulel.DataSetTableProducerlFormatCell(Sender: TObj ect; CellRow, CellColumn: Integer; var BgColor: THTMLBgColor; var Align: THTMLAlign; var VAlign: THTMLVAlign; var CustomAttrs, CellData: String) ; begin if (CellColumn = 0) and (CellRow <> 0) then CellData : = '<a href="' + ScriptName + '/record?LastName=' + dataEmployee [ ' LastName' ] + ' &FirstName=' + dataEmployee [ ' FirstName' ] + ' "> ' + CellData + ' </a>'; end;


La figura 20.3 muestra el resultado de esta funcion. Cuando el usuario selecciona uno de 10s vinculos, se llama de nuevo a1 programa y puede comprobar la lista de cadena Q u e r y F i e l d y estraer 10s parametros desde la URL. Es entonces cuando utiliza 10s valores correspondientes a 10s campos de la tabla utilizados para la busqueda del registro (basada en la llamada a F i n d N e a r e s t ) .

[I

Web Broker Demo Number

Last Name

First Name

Phone Ext.

L

Hire Date

Salary

2

Nelson

Robert

250

12/28/1988

105900

4

young

Bruce

233

12/28/1988

97500

5

Lambcrt

Kun

22

2/6/1989

102750

2-

Johnson

Leshe

410

41511989

64635

9

Forest

Phil

229

4/17/1989

j i 0 W D '

-

-

--

-

75060

w

=='

f Figura 20.3. La sahda correspondiente a la ruta table del ejemplo BrokDemo, que genera una tabla HTML con h~pervinculosinternos. procedure TWebModulel.RecordAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); begin dataEmployee.0pen; // va a 1 registro solicitado dataEmployee .Locate ( ' L A S T N A M E ; F I R S T N A M E r, VarArrayOf([Request.QueryFields.Values['LastName'], Request .QueryFields .Values [ 'FirstNdrnel] ] ) , [ ] ) ; / / obtiene l a salida Response.Content : = Response.Content + DataSetPage.Content; end;

Consultas y formularios El ejemplo anterior utilizaba algunos componentes productores de HTML presentados con anterioridad, per0 hay otro componente de este grupo que no hemos utilizado aun: el QueryTableProducer (para BDE) y su hermano SQLQueryTableProducer (para dbEspress). Como veremos, este componente hace que la creacion de programas complejos de bases de datos sea algo muy sencillo. Supongamos que queremos buscar algunos clientes en una base de datos. Para ello, podriamos


crear el siguiente formulario HTML (incrustado en una tabla HTML para que tenga un formato mejor): <hl>Customer QueryProducer Search Form</h4> method="POSTW> <form action="/scripts/CustQueP.dll/search" <table> <trXtd>State:</td> <td><input type="textn n a m e = " S t a t e W X / t d X / t r > <trXtd>Country:</td> < t d X i n p u t type="textn n a m e = " C o u n t r y " > < / t d X / t r > < t r X t d X / td> < t d X c e n t e r X i n p u t type="Submit"></center></td></tr> </table></f o m >

NOTA: A1 igual que en Delphi, un formulario HTML contiene una serie de controles. Existen herramientas visuales que ayudan a disefiar estos formularios, o tambien se puede escribir manuaimente el &dig0 HTML.Entre 10s controles disponibles se incluyen botones, cuadros de edicibn, (selecciones) cuadros combinados y botones de entrada (o de radio). Tambien se pueden . .* . . . .. . . . aerinir oorones con ripos. especmcos como ae envlo o reinlciallzacion. un elemento muy importante de 10s formularios es el metodo de envio, que puede ser POST (10s datos se envian de forma oculta, y se reciben en la . . - . - . . - - - .. . . . .. propledad c o n t e n t F i e l d s ) o GET (10s datOS se pasan como parte ael URL, y se pueden extraer de la propiedad Q u e r y F i e l d s ) . >-.-~-1-L-*.

.

.

a

-

P

3.

I

. . ! - l . ..tl.

.I.

-

Hay un elemento importante que debemos tener en cuenta en el formulario: 10s nombres de 10s componentes de entrada (State y Country) deberian de coincidir con 10s parametros de un componente SQLQuery: SELECT customer, FROM CUSTOMER WHERE

State-Province

State-Province,

Country

= :State OR Country = :Country

Este codigo se utiliza en el ejemplo CustQueP. Para crearlo, hemos puesto un componente SQLQuery dentro del WebModule y hemos generado 10s objetos de campo adecuados. En el mismo WebModule hemos aiiadido un componente SQLQueryTableProducer que se encuentra conectado a la propiedad P r o d u c e r de la accion / s e a r c h . El programa genera la respuesta adecuada. Cuando se activa el componente SQLQueryTableProducer, llamando a su funcion C o n t e n t , este inicia el componente SQLQuery obteniendo 10s parametros de la solicitud HTTP. El componente puede examinar automaticamente el metodo de solicitud y luego utilizar la propiedad Q u e r y F i e l d s (si la solicitud es una solicitud GET) o la propiedad C o n t e n t F i e l d s (si la solicitud es POST). Un problema derivado del uso de un formulario HTML estatico (como el anterior), es que no indica 10s estados y paises que se pueden buscar. Para solucionar


este problema, podemos utilizar un control de seleccion en lugar de un control de edicion en el formulario HTML. Sin embargo, si el usuario aiiade un nuevo registro a la tabla de la base de datos, tendremos que actualizar la lista de elementos automaticamente. Como solucion final, podemos diseiiar la DLL ISAPI para producir un formulario sobre la marcha y rellenar 10s controles de seleccion con 10s elementos disponibles. Generaremos el HTML para esta pagina en la accion / f o r m , que esta conectada con un componente PageProducer. El PageProducer contiene el siguiente texto HTML que incluye dos etiquetas especiales: <h4>Customer Queryproducer Search Form</h4> <form action="CustQueP.dll/search" method="POST"> <table> <trXtd>State:</td> <tdXselect name="StateW><option> </ option><#State-Province></select~/td~/tdX/tr>

<trXtd>Country:</td> <tdXselect name="Country"><option> </option><#Country></ selectX/tdX/tr> <trXtdX/td> <tdXcenterXinput t y p e = " S u b m i t " X / c e n t e r X / t d X / t r > </ tableX/f o m >

Observara que las etiquetas tienen el mismo nombre que algunos campos de la tabla. Cuando el PageProducer se encuentra con una de estas etiquetas, aiiade una etiqneta HTML < o p t ion> para cada valor del campo correspondiente. Veamos el codigo del controlador del evento OnTag, que es bastante generic0 y reutilizable: procedure TWebModulel.PageProducerlHTMLTag(Sender: TObject; Tag: TTag; const TagString: String; Tagparams: TStrings; var ReplaceText : String) ; begin ReplaceText : = " ; SQLQuery2.SQL.Clear; SQLQuery2.SQL.Add ('select distinct ' + TagString + ' from customer') ; try Query2.0pen; try SQLQuery2.First; while not Query2.EOF do begin ReplaceText : = ReplaceText + ' <option>' + Query2. Fields [ O ] .Asstring + ' </ option>'#13; SQLQuery2.Next; end; finally SQLQuery2.Close; end;


except ReplaceText end; end;

:=

'(wrong field: ' + Tagstring

+

I ) ' ;

Este metodo utiliza un segundo componente SQLQuery,que hemos colocado manualmente en el formulario y conectado a un componente SQLConnection compartido. La figura 20.4 muestra la salida de este formulario.

State

Canada England

FijI France Hang Kong Italy Japan Netherlands Switzerland USA

1

Figura 20.4. La accion de formulario del ejemplo CustQueP produce un formulario HTML con un componente de selection que se actualiza dinamicamente para reflejar el estado actual de la base de datos.

Esta extension del servidor Web, como muchas otras que hemos creado, permite a1 usuario ver 10s detalles de un registro especifico. Igual que en el ejemplo anterior, esto se puede llevar cab0 personalizando la salida de la primera columna (la columna cero), que es generada por el componente QueryTableProducer: procedure TWebModulel.QueryTableProducerlFormatCell( Sender: TObject; CellRow, CellColumn: Integer: var BgColor: THTMLBgColor; var Align: THTMLAlign; var VAlign: THTMLVAlign; var CustornAttrs, CellData: String); begin i f (CellColumn = 0 ) and (CellRow <> 0 ) then CellData : = ' < a href="' + Request.ScriptName + ' / record?Company=' + CellData + r 11, I + CellData + ' < / a > ' # 1 3 ; i f CellData = " then CellData : = ' & n b s p ; ' ; end;


TRUCO: Cuando hay una celda vacia en una tabla HTML,la mayoria de 10s navegadores la representan sin borde. Es por esto que hemos aaadido un caracter de espacio (&nbsp;) en cada celda vacia. Es necesario hacer esto en cada tabla HTML generada por 10s productores de tablas de Delphi. La accion para este vinculo es / r e c o r d y hay que pasar un elemento especifico despues del parametro ? (sin el nombre del parametro; que no es estandar). El codigo que utilizamos para producir las tablas HTML para 10s registros no utiliza componentes productores como hemos venido haciendo hasta ahora, sino que muestra 10s datos de cada campo en una tabla personalizada: p r o c e d u r e TWebModulel.RecordAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); var I: Integer; begin i f Request. QueryFields .Count = 0 then Response.Content : = ' R e c o r d not found' else begin Query2.SQL.Clear; Query2.SQL.Add ( ' s e l e c t * f r o m customer ' + ' w h e r e Company="' + Request .QueryFields .values [ ' C o m p a n y ' ] + ' "' ) : Query2.0pen; Response. Content : = '<htn~l><head><title>CustomeR r ecord</title></ h e a d > < b o d y > ' # 13 + ' <hl>Customer Record: ' + Request .QueryFields [O] + ' </ hl>'#13 + ' <table border,' #l3; f o r I : = 1 t o Query2.FieldCount - 1 d o Response.Content : = Response-Content + ' < t r > < t d > ' + Query2. Fields [I].FieldName + ' </ td>'#13'<td>' + Query2.Fields [I].AsString + ' < / t d > < / t r > ' # 1 3 ; Response .Content := Response .Content + ' </table><hr>'#13 + / / e n l a c e a 1 formulario d e consulta ' <a href=lV' + Request. ScriptName + ' / f o r m n > ' + ' Next Query < / a > '# I 3 + ' < / b o d y > < / h t m l > '# l 3 ; end; end:

Trabajo con Apache Si tenemos la intencion de utilizar Apache en lugar de IIS o cualquier otro servidor Web, podemos sacar partido de la tecnologia CGI para usar nuestras


aplicaciones en casi cualquier servidor Web. Sin embargo, esta opcion significa una reduccion en la velocidad y algunos problemas a la hora de manejar informacion de estado (ya que no podemos guardar ningun dato en memoria). Esta es una buena razon para escribir una aplicacion ISAPI o un modulo dinamico de Apache. Utilizando la tecnologia WebBroker de Delphi, podemos compilar facilmente el mismo codigo para ambas aplicaciones de forma que sea mucho mas sencillo Ilevar el programa a una plataforma Web diferente. Tambien podemos recompilar un programa CGI o un modulo dinamico de Apache con Kylix y utilizarlo en un servidor Linus. Como ya hemos comentado, Apache ejecuta aplicaciones CGI tradicionales y tambien utiliza una tecnologia cspecifica para guardar el programa de estension del servidor cargado sicmpre en memoria para conseguir una respuesta mas rapida. Para crear este programa en Delphi, simplemente hay que utilizar la opcion Apache Shared Module del cuadro de dialogo New Server Application: hay quc escoger entre Apache 1 o Apache 2, segun la version del servidor que vaya a utilizarse.

ADVERTENCIA: Mientras que Delphi 7 soporta la version 2.0.39 de Apache, no soporta la mas actual y popular version 2.0.40, debido a un cambio en las interfaces de biblioteca. No se recomienza el uso de la version 2.0.39 ya que tiene un pioblema de seguridad. Hay disponible informacion sobre como ada~tarel cbdiao de la VCL v conseguir - aue . 10smodulos Sean compatibles con Apache 2.0.40 y superior en 10s grupos de noticias, gracias a 10s miembros del equipo de investigation y desarrollo de Borland. Actualmente se encuentra en el sitio Web de Bob Swart en el U I U

-

.. .-.

.

,.

.

..-a

a , .

Si se decide crear un modulo de Apache, se obtendra una biblioteca que contiene el siguiente tip0 de codigo fuente para este proyecto: library Apachel; uses WebBroker, ApacheApp, ApacheWm i n 'ApacheWm.pas' (WebModulel: TWebModule);

exports apache-module

name

'apachel-module';

begin Application.Initialize;


Application.CreateForm(TWebModulel, WebModulel); Application.Run; end.

Preste particular atencion a la clausula exports, que indica el nombre utilizado por 10s archivos de configuracion de Apache para hacer referencia a1 modulo dinamico. En el codigo fuente del proyecto, podemos aiiadir dos definiciones mas: el nombre del modulo y el tip0 de contenido, de la siguiente manera: ModuleName : = ContentType: =

' Apachel-module'

;

'Apachel-handler1;

Si no se establecen estos valores, Delphi les asignara valores predeterminados, que se construyen aiiadiendo 10s sufijos module y -handler a1 nombre de proyecto (con lo que se consiguen 10s dosiornbres que hemos comentado). Un modulo de Apache no suele desplegarse dentro de una carpeta de guiones, sino dentro de la subcarpeta modules del servidor (de manera predefinida, C:\Archivos de programa\Apache\modules). Hay que editar el archivo http.conf y aiiadirle una linea para cargar el modulo, de este modo: LoadModule

apachel-module

modules/apachel.dll

Finalmente, tenemos que indicar cuando se invocara el modulo. El controlador definido por el modulo puede asociarse con una extension de archivo dada (para que el modulo procese todos 10s archivos que tengan esa extension) o con una carpeta fisica o virtual. En el ultimo caso, la carpeta no existe per0 Apache simula que esta alli. De esta manera podemos establecer una carpeta virtual para el modulo de Apache 1:

Ya que Apache tiene en cuenta las maylisculas (debido a su herencia de Linux), tambien podria desearse aiiadir una segunda carpeta virtual, en minusculas:

Ahora se puede llamar a la aplicacion de muestra mediante el URL http:// localhost/Apachel. Una gran ventaja del uso de carpetas virtuales en Apache es que un usuario no distingue realmente entre las partes fisicas y dinamicas del sitio, tal y como puede comprobarse si se experimenta con el ejemplo Apachel (que incluye el codigo aqui comentado).

Ejemplos practicos Despues de esta presentacion general del desarrollo de aplicaciones de semidor con WebBroker, completaremos esta parte del capitulo con dos ejemplos practicos. El primer0 es un clasico contador Web. El segundo es una ampliacion


del programa WebFind del capitulo 19, que genera una pagina dinamica en lugar de rellenar un cuadro de lista.

Un contador Web grafico de visitas Las aplicaciones de servidor que hemos creado hasta ahora estaban basadas imicamente en texto. Por supuesto, se pueden aiiadir facilmente referencias a archivos graficos existentes. Sin embargo, lo que resulta mas interesante es crear programas de servidor capaces de generar graficos que cambien con el tiempo. Un ejemplo tipico es un contador de visitas de pagina. Para escribir un contador Web, hay que guardar el numero actual de visitas en un servidor y despuis leer e incrementar el valor cada vez que se llama a1 programa contador. Para devolver esta informacion, todo lo que se necesita es texto HTML con el numero de visitas. El codigo es sencillo: procedure TWebModulel.WebModulelWebActionItemlAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean) ; var nHit : Integer; LogFile : Text; LogFileName: string; begin LogFileName := ' WebCont log'; System.Assign (LogFile, LogFileName) ; try / / lee si existe el archivo if FileExists (LogFileName) then begin Reset (LogFile); Readln (LogFile, nHit) ; Inc (nHit); end else nHit : = 0; / / g u a r d a e l n u e v o da t o Rewrite (LogFile); Writeln (LogFile, nHit) ; finally Close (LogFile); end; Response .Content : = IntToStr (nHit); end;

.

-

-

--

- -- - --

-

- - ---

-

-

-

-- -

ADVERTENCIA: Este manejo simple de archivo no escala. Cuando vanos visitantes accedan a la pirgina a la vez, el c6digo puede devolver resultados falsos o fallar con un error de entraddsalida a1 archivo debido a qne


'

una peticidn de o6a hebra tenga abieho el archivo p a i a 7 z 6 r a m i e - n t r esta otra hebra trate de abrir el archivo para escritura. Para soportar una situacibn parecida, sera necesario utilizar un rnutex (o una seccion critica en un programa multihilo) para permitir que cada hebra espere hasta que la hebra actual deje de utilizar el archivo cuando complete su tarea.

Es mas interesante crear un contador grafico que pueda incrustarse facilmente cn cualquier pagina HTML. Hay dos enfoques para crear un contador grafico: se puede preparar una imagen de bits para cada digito y combinarlos en el programa, o dejar que el programa dibuje sobre una imagen en memoria para producir el grafico quc se quiere devolver. En el programa WebCount hemos escogido la segunda tdcnica. Basicamente, puede crearse una componente Image que contenga una imagen en memoria, que puede pintarse mediante 10s metodos habituales de la clase TCanvas. Despues se puede conectar esta imagen a un objeto TJpegImage. A1 acceder a la imagen a travds del componente JpegImage la imagen se convierte a1 formato JPEG. Despues pueden guardarse 10s datos JPEG en un flujo y devolverlos. Como puede versej consiste en muchos pasos, pero el codigo no es complicado: / / c r e a una i m a g e n e n m e m o r i a Bitmap : = TBitmap.Create; t rY Bitmap.Width : = 120; B i t m a p - H e i g h t : = 25; // d i b u j a 1 0 s d i g i t o s Bitmap. Canvas. Font. Name : = ' A r i d 1 '; Bitmap.Canvas.Font.Size : = 14; Bitmap.Canvas. Font .Color : = RGB (255, 127, 0) ; Bitmap.Canvas.Font.Sty1e : = [fsBold]; Bitmap.Canvas .Textout (1, 1, ' H i t s : ' + FormatFloat ( ' # # # , # # # , # # # I , Int (nHit)) ) ; // c o n v i e r t e a JPEG y m u e s t r a Jpegl : = TJpegImage.Create; t rY Jpegl.CompressionQua1ity : = 50; Jpegl .Assign (Bitmap); S t r e a m : = TMemoryStream.Create; Jpegl SaveToStream (Stream); Stream.Position : = 0; Response.ContentStream : = Stream; Response. ContentType := ' i m a g e / j p e g ' ; Response.SendResponse; //el o b j e t o d e respuesta liberard el f l u j o finally Jpegl.Free; end; finally

.


Bitmap. Free; end;

Las tres sentencias responsables de devolver la imagen JPEG son las dos que fijan las propiedades C o n t e n t s t r e a m y C o n t e n t T y p e de R e s p o n s e y la llamada final a S e n d R e s p o n s e . El tipo de contenido debe corresponderse con uno de 10s posibles tipos MIME aceptados por el navegador, y el orden de estas tres sentencias es importante. El objeto R e s p o n s e tambidn time un metodo S e n d s t r e a m , pero solo deberia llamarse despues de enviar el tipo de 10s datos con una llamada independiente. Este es el efecto de este programa:

Para incrustar el programa en una pagina, hay que aiiadir el siguiente codigo a1 codigo HTML:

Busquedas con un motor Web de bkquedas En el capitulo 19 analiz'amos el uso del componente cliente HTTP de Indy para conseguir el resultado de una busqueda en el sitio Web de Google. Ampliaremos este ejemplo, convirtiendolo en una aplicacion de servidor. El programa Websearcher, disponible como aplicacion CGI o como ejecutable de Web App Debugger, time dos acciones: la primera devuelve el codigo HTML conseguido mediante el motor de busqueda, y el segundo procesa el HTML para rellenar un componente de conjunto de datos de cliente, que esta conectado con un productor de paginas de tabla para generar el aspect0 final. Este es el codigo para la segunda seccion: cons t strsearch = ' h t t p : / / w w w . g o o g l e . corn/ s e a r c h ? a s - q = b o r l a n d + d e l p h i & n ~ m = l O O'; procedure TWebModulel.WebModulelWebActionItemlAction(Sender: T O b j ect ; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean) ; var I : integer;


begin i f not cds .Active then cds.CreateDataSet else cds.EmptyDataSet; for i : = 0 to 5 do / / n u r n e r o d e p d g i n a s begin // c o n s i g u e e l f o r r n u l a r i o d e d a t o s d e l s i t i o d e b u s q u e d a GrabHtml (strSearch + ' & s t a r t = ' + IntToStr (i*100)); // l o a n a l i z a p a r a r e l l e n a r e l c d s HtmlStringToCds; end; cds .First; // d e v u e l v e e l c o n t e n i d o d e l p r o d u c t o r Response.Content : = DataSetTableProducerl-Content; end:

El metodo G r a b H t m l es identico al ejemplo WebFind. El metodo HtmlStringToCds es parecido a1 metodo correspondiente del ejemplo WebFind (que aiiade 10s elementos a un cuadro de lista): aiiade las direcciones y sus textos descriptivos mediante la Ilamada: cds . InsertRecord

(

[O, strAddr, strText] )

;

El componente ClientDataSet se configura con tres campos: dos cadenas mas un contador de linea. Este campo vacio adicional es necesario para incluir la columna adicional en el productor de la tabla. El codigo rellena la columna en el evento de formato de celda, que aiiade tambien el hiperenlace: procedure TWebModulel.DataSetTableProducerlFormatCell(Sender: TObject; CellRow, CellColumn: Integer; var BgColor: THTMLBgColor; var Align: THTMLAlign; var VAlign: THTMLVAlign; var CustomAttrs, CellData: String); begin i f CellRow <> 0 then case CellColumn of 0 : CellData := IntToStr (CellRow); 1: CellData : = ' < a h r e f = " ' + CellData + "'>' + SplitLong (CellData) + ' < / a > '; 2 : CellData : = SplitLong (CellData); end; end;

La llamada a SplitLong se utiliza para afiadir espacios adicionales en el texto de salida, para evitar que las columnas de la cuadricula Sean demasiado grandes (el navegador no dividira el texto en varias lineas a no ser que contenga espacios u otros caracteres especiales). El resultado de este programa es una aplicacion bastante lenta (debido a las varias peticiones HTTP que debe reenviar) que produce una salida con el aspect0 que muestra la figura 20.5.


d e ~ p h amp. I+ jhimr owe

cod

Figura 20.5. El programa Websearch muestra el resultado de varlas bdsquedas realizadas mediante Google.

WebSnap Ahora que hemos presentado 10s elementos mas importantes del desarrollo de 10s clementos dc aplicaciones de servidores Web con Delphi, podemos centrarnos en una arquitcctura mas comple.ja disponible desde Delphi 6: WebSnap. Habia dos buenas razones para no abordar este tema desde el principio. Una de cllas es que WcbSnap se base en 10s conceptos que proporciona WebBroker, por lo que si no conocemos las caracteristicas subyacentes, no podremos comprender las mas nuevas. Por e.jemplo, una aplicacion WebSnap tecnicamentc es un programa CGI; o un modulo ISAPI o de Apache. La segunda razon es que desde WebSnap solo sc incluye en la version Enterprise Studio de Delphi, y no todos 10s programadores de Delphi tienen la posibilidad de utilizarla. WebSnap tiene unas cuantas ventajas sobre WebBroker, ya que pcrmite utilizar multiples modulos Web, cada uno para una pagina, la integracion de guiones de servidor, XSL, y la tecnologia Internet Express (de estos dos ultimos temas hablaremos mas adelante, en el capitulo 22). Aun mas, dispone de muchos componentes listos para ser usados para mane.jar tareas comunes tales como el registro de un usuario, gestion de sesiones, etc. En lugar de dar una lista con todas las caracteristicas de WebSnap, vamos a analizarlas con una serie de sencillas aplicaciones centradas en las mismas. Por motivos de comprobacion, todas estas aplicaciones han sido creadas utilizando eI Web App Debugger, pero no sera dificil desplegarlas usando cualquier otra tecnologia. El punto de partida del desarrollo de una aplicacion WebSnap es un cuadro de dialogo que podemos invocar bien desde la pagina WebSnap del cuadro de dia-


logo New Items (File>New>Other) o bien utilizando la barra de herramientas Internet del IDE. El cuadro de dialogo resultante, que muestra la figura 20.6, nos permite escoger el tip0 de aplicacion (como una aplicacion WebBroker) y personalizar 10s componentes iniciales de la aplicacion (despues podremos ariadir alguno mas). La parte inferior del dialogo determina el comportamiento de la primera pagina (generalmente, la pagina predeterminada o de inicio del programa). Un cuadro de dialogo similar se mostrara tambiCn para paginas posteriores.

Figura 20.6. Las opciones ofrecidas por el cuadro de dialogo New WebSnap Application incluyen el tip0 de servidor y un boton que permite seleccionar 10s cornponentes basicos de la aplicacion.

Si escogemos 10s valores predefinidos y escribimos un nombre para la pagina de inicio, el cuadro de dialogo creara un proyecto y abrira un TWebAppPageModule.Este modulo contiene 10s componentes que hemos escogido, que son de manera predeterminada: Un componente WebAppComponents: Es un contenedor para todos 10s servicios centralizados de la aplicacion WebSnap, tales como la lista de usuario, repartidores basicos, servicios de sesion, etc. No es obligado activar todas estas propiedades, ya que puede haber alguna aplicacion que no necesite todos 10s servicios.

El componente PageDispatcher: Ofrece uno de estos servicios basicos, y alberga automaticamente una lista de las paginas disponibles de la aplicac i h , al mismo tiempo que define la pagina predeterminada.


El componente AdapterDispatcher: Manipula 10s formularies HTML de envio y las peticiones de imageries. ApplicationAdapter: Es el primer componente de la familia de 10s adaptadores. Estos componentes proporcionan campos y funciones a 10s guiones de servidor evaluados por el programa. Concrctamente, el ApplicationAdapter es un adaptador de campos que muestra el valor de su propiedad A p p 1ica t ionT it le. Si introducimos un valor para esta propiedad, 10s guiones tambien podran disponer de ella. Un PageProducer: El modulo conticne un PageProducer que incluye el codigo HTML dc la pagina, en este caso la pagina predeterminada del programa. A diferencia de las aplicaciones WebBroker, el HTML para este componente no se almacena dentro de su propiedad HTMLDoc de lista de cadena, ni se hace referencia a el mediante su propiedad HTMLFile.El archivo HTML es un archivo externo, almacenado de manera predeterminada en la carpeta que alberga cl codigo fuente del proyecto y que se referencia desde la aplicacion mediante una sentencia similar a una sentencia de inclusion de recurso: { * . h t m l } Archivo HTML: Dado que el archivo HTML incluido por el PageProducer se guarda como un archivo independiente (el componente LocateFileService nos ayudara en su despliegue), podemos editarlo para cambiar la salida de una pagina del programa sin necesidad de recompilar la aplicacion. Gracias a1 soporte del guiones de servidor, estos cambios no se refieren solamente a la parte fija del archivo HTML, sino tambien a su contenido dinamico. A1 basarse en una plantilla estandar, el archivo HTML por defecto ya contiene algunos guiones. ---

---

-

-

-

-

..

--

ADVERTENCIA: El parecido entre incluir recursos y las referencias a HTML es basicamente sintictico. La referencia HTML se usa ~ 6 1 0para encontrar en tiempo de disefio el archivo, mientras que la directiva de inclusion de un recurso enlaza 10s datos a 10s que hace referencia con el archivo

..~ -

L - , x # -

Gracias a esa directiva es posible ver el archivo HTML dentro el editor de Delphi con un buen resaltado de sintaxis. Simplemente tendremos que seleccionar la solapa inferior correspondiente. El editor tambien tiene otras paginas para un modulo WebSnap, incluyendo de manera predefinida una pagina HTML Result en donde podemos ver el HTML generado despuis de evaluar 10s guiones y una pagina Preview que contiene lo que el usuario vera en un explorador. El editor de Delphi 7 para un modulo WebSnap tambien incluye un editor HTML mucho mas potente que el de Dephi 6; incluye un resaltado de sintaxis y unas prestaciones de completitud de codigo mucho mejores. Si se prefiere editar el codigo HTML de la


aplicacion Web con otro editor mas sofisticado, podemos determinar esta eleccion en la ficha Internet del cuadro de dialog0 Environment Options. A1 hacer clic sobre el boton Edit para la extension HTML, podremos escoger un editor externo para el menu de metodo abreviado del editor o un boton especifico de la barra de herramientas de Internet de Delphi. La plantilla HTML estandar utilizada por WebSnap aiiade a cualquier pagina del programa su titulo y el titulo de la aplicacion, utilizando lineas de guion como las siguientes: <hl><%= Application.Title %></hl> <h2><%= Page .Title %></h2>

Mas adelante hablaremos sobre 10s guiones. Por ahora vamos a comenzar el desarrollo del ejemplo WSnapl creando un programa con varias paginas. Pero antes, vamos a completar este repaso mostrando el codigo fuente de un modulo de una pagina Web de muestra: type Thome = class (TWebAppPageModule) end; function home : Thome; implementation

{SR . d f m )

{.htzd)

uses WebReq, WebCntxt , WebFact

,

Variants;

function home : Thome ; begin Result end;

: = Thome (Webcontext.FindModuleClass (Thome)) ;

initialization if WebRequestHandler <> nil then WebRequestHandler.AddWebModu1eFactory (TWebAppPageModuleFactory.Create(Thome, TWebPageInfo. Create([wpPublished { , wpLoginRequired)], ' . h t z d l ) , caCache) ) ; end.

El modulo utiliza una funcion global en vez de un objeto global tipico de formularios para soportar el almacenamiento en cache de las paginas. Esta aplicacion tambien tiene codigo adicional en la seccion de inicializacion (concretamente codigo de registro) para permitir que la aplicacion sepa c u d es la funcion de la pagina y su comportamiento.


- - -- -- - ---- - ..TRUCO:A1 contrario que en 10s ejempIos de WebBroker de este capitulo, 10s ejemplos de WebSnap se wmpilan cada uno en su propia carpeta. Se hace asi porque 10s archivos HTML se riecesitan en tiempo de ejecucion y hemos preferido simplificar el despliegue. -

- --

A

Gestion de varias paginas La diferencia mas notable entre WebSnap y WebBroker es que, en lugar de tener un unico modulo de datos con diversas funciones, conectadas finalmente a componentes productores, WebSnap tiene varios modulos de datos que se corresponden cada uno con una accion y que tienen un componente productor con un archivo HTML conectado. Podemos aiiadir diversas acciones a una pagina o modulo, per0 la idea basica es estructurar las aplicaciones en torno a paginas y no a acciones. A1 igual que con las acciones, el nombre de la pagina se indica en la ruta de la peticion. Como ejemplo, hemos aiiadido dos paginas mas a la aplicacion WebSnap (creada con 10s valores predefinidos). Para la primera pagina, en el dialogo New WebSnap Page Module (vease figura 20.7), hemos escogido un productor estandar de pagina y lo hemos llamado date. Para la segunda, hemos preferido un DataSetPageProducer y lo hemos llamado country. Despues de guardar 10s archivos podemos probar la aplicacion. Gracias a algunos guiones, que veremos mas adelante, cada pagina lista todas las paginas que estan disponibles (a menos que hayamos desactivado la casilla de verificacion Published del dialogo New websnap Page Module).

Figura 20.7. El cuadro de dialogo New WebSnap Page Module.

E m


Las paginas estaran casi vacias, per0 a1 menos tendremos la estructura adecuada. Para completar la pagina de inicio, simplemente editaremos directamente el archivo HTML vinculado. Para la pagina date, hemos empleado el mismo metodo que para una aplicacion de WebBroker, aiiadiendo a1 texto HTML unas etiquetas personalizadas, como la siguiente: <p>The t i m e at this site is <#time>.</p>

Tambien hemos aiiadido algo de codigo a1 controlador del evento OnTag del componente productor para reemplazar esta etiqueta con la hora actual. Para la segunda pagina, country, hemos modificado el codigo HTML incluyendo etiquetas para 10s diversos campos de la tabla de pais, como en:

Despues conectamos el ClientDataSet a un productor de pagina: o b j e c t DataSetPageProducer: DataSet = cdscountry end

TDataSetPageProducer

o b j e c t cdscountry: TClientDataSet FileName = 'C:\Archivos de programa\Archivos comunes\Borland

Shared\Data\country.cdsl end

Para abrir este conjunto de datos cuando la pagina se crea por primera vez, y devolverla a1 primer registro en futuras llamadas, hemos manipulado el evento OnBe f o r e D i s p a t c h P a g e del modulo de la pagina Web, aiiadiendole este codigo:

Es bastante importante el hecho de que una pagina WebSnap sea muy similar a una parte de una aplicacion WebBroker (basicamente una accion ligada a un productor), ya que nos permite adaptar componentes del codigo WebBroker a esta nueva arquitectura. Incluso se pueden adaptar 10s componentes DataSetTableProducer a esta arquitectura. Tecnicamente, podemos generar una pagina nueva, quitar su componente productor, sustituirlo por un DataSetTableProducer y conectar este componente a la propiedad P a g e P r o d u c e r del modulo de la pagina Web. En la practica, este metodo recortara el archivo HTML de la pagina y sus guiones. En el programa WSnap1 hemos utilizado una tecnica mejor a1 aiiadir a1 archivo HTML una etiqueta personalizada (< # h tml t a b 1 e >). Tambien hemos utilizado el evento OnTag del productor de la pagina para aiiadir el resultado de tabla del conjunto de datos a1 HTML: if Tagstring = ' h t r d t a b l e ' then ReplaceText : = DataSetTableProducer1.Content;


Guiones de servidor Cuando tenemos varias paginas en un programa de servidor (cada una de ellas asociada a un modulo de pagina diferente), la forma de escribir un programa cambia. Tener a mano 10s guiones de servidor nos permite utilizar un enfoque aun mas potente. Por ejemplo, 10s guiones estandar del ejemplo WSnap 1 se encargan de la aplicacion, 10s titulos de las paginas y el indice de las mismas. Este indice lo genera un cnumerador, es decir, la misma tecnica que se utiliza para recorrer una lista dentro del codigo de un guion WebSnap: < t a b l e cellspacing="O" cellpadding="O"><td> <X e = n e w Enumerator(Pages) s = " c = o for ( ; ! e. atEnd ( ) ; e .moveNext (

if (e.item() .Published) (

if ( C > 0 ) s += ' & n b s p ;I & n b s p ; ' if (Page.Name ! = e.item ( ) .Name) s += ' < a href="' + e.item() .HREF + e.item() .Title + ' < / a > ' else s += e.item() .Title

I " > '

+

C++

1 1 if ( c > l ) Response .Write (s) '

>

< / td></table> .

.

-

NOTA: Generahente, 10s guiones de WebSnap e s t h escritos en JavaScript, un lenguaje basado en objetos muy comun en la programacion para Internet ya que es el unico lenguaje de guiones que suele estar disponible en navegadores (en el cliente). JavaScript (que tecnicamente se llama ECMAScript) toma prestada la sintaxis bhsica del lenguaje C y no tiene casi nada que ver con Java. Realmente, el WebSnap utiliza el motor Activescripting de Microsoft que soporta JScript (una variante de JavaScript) y VBScript. Dentro de la unica celda de esta tabla (que no tiene filas, aunque parezca extraiio)? el guion produce una cadena mediante el metodo Response .Write. Esta cadena se crea con un bucle f o r sobre un enumerador de paginas de la aplicacion, guardado en la entidad global Pages. El titulo de cada pagina se aiiade a la cadena solamente si la pagina se publica. Cada titulo utiliza un hipervinculo, except0 para la pagina actual. A1 tener este codigo en un guion en


lugar de estar incrustado en un componente Delphi, podemos pasarselo a un buen diseiiador Web para que lo convierta en algo mas atractivo visualmente.

TRUCX$~Fiqa h a w publicar p a phgina o invertir d i d o estado, no hay qoe frjarse en ningunapmpledad d d m 6 d d o & hi $hg&i Web. Estc estado s~icontfolacod nn indici&x d&meto& kdd~izbEf6dule~actc)r~, UamId.0 cn el &digb de in&&;ic~n del mMbltrld de hp&* Web. Simplemente mitntarribE 0n6dicf.mjndicador para ccmseguird efecto deseado. Como muestra de lo que podemos hacer mediante 10s guiones de lenguajes interpretados, hemos aiiadido a1 ejemplo WSnap2 (una ampliacion de WSnap l), una pagina demoscript. El guion de esta pagina puede generar una tabla completa; llena de valores multiplicados utilizando el siguiente codigo (vease la figura 20.8): <table border=l cellspacing=O> <tr> <th>&nbsp;</th> <% for (j=l;j<=5;j++) ( %> <th>Column <%=j %></th> <%

%>

</tr> < % for (i=l;i<=5;i++) ( %> < tr> < td>Line <%=i %></td> < % for (j=l;j<=5;j++) ( %> < td>Value= <%=i * j %></td> <% } B> </tr> <B } %> </table>

En este guion, el simbolo <%=reemplaza a la orden Response .W r i t e . Otra caracteristica importante de la escritura de guiones de sewidor es la inclusion de unas paginas dentro de otras. Por ejemplo, si queremos modificar el menu, podemos incluir el codigo HTML relacionado y el guion en un unico archivo, en lugar de cambiarlo y mantenerlo en varias paginas. Incluir un archivo es tan sencillo como usar esta sentencia:

En el listado 20.1, podemos encontrar el codigo fuente completo del archivo incluido para el menu, a1 que se hace referencia desde todos 10s demas archivos HTML del proyecto. La figura 20.9, muestra un ejemplo de este menu, que se muestra en la parte superior de la pagina mediante el guion de generacion de tabla que ya hemos mencionado.


<hr> <hl><%= Page. Title %></hl> <P>

Este guion para el menu utiliza la lista Pages y 10s objetos globales de guion Page y Application. WebSnap permite disponer de otros objetos globales tales como EndUser y Session (en caso de que se aiiadan 10s correspondientes adaptadores a la aplicacion), Modules y Producer, que permite acceder a1 componente Producer del modulo de la pagina Web. El guion tambien permite usar 10s objetos Response y Request del modulo Web.

Adaptadores Ademas de a estos objetos globales, dentro de un guion tambien podemos acceder a todos 10s adaptadores que esten disponibles en el modulo de la pagina Web correspondiente. (Adaptadores de otros modulos, como 10s modulos de datos Web compartidos, deben de ser referenciados prefijando su nombre con el objeto Modules y el correspondiente modulo.) La finalidad de 10s adaptadores es pasar informacion del codigo cornpilado de Delphi al guion interpretado, proporcionando una interfaz que puedan utilizar guiones para la aplicacion Delphi. Los adaptadores contienen campos que representan datos y albergan funciones que representan ordenes. Los guiones de servidor pueden acceder a estos valores y lanzar estas ordenes, pasandoles parametros especificos.

Campos de adaptadores Para realizar personalizaciones sencillas, simplemente bastara con aiiadir nuevos campos a 10s adaptadores especificos. En el ejemplo WSnap2, hemos aiiadido un campo personalizado al adaptador de la aplicacion. Despues de seleccionar este componente, podemos optar por abrir su editor Fields (accesible a traves de su menu local) o trabajar dentro del Object Treeview. Despues de aiiadir un campo nuevo (Ilamado AppHitCount en el ejemplo), podemos asignarle un valor en su evento OnGe tvalue. Si queremos contar las solicitudes de cada pagina de la aplicacion Web, tambien podemos controlar el evento OnBef orePageDispat ch del componente PageDispatcher global para incrementar el valor de un campo local, HitCount. Este es el codigo para 10s dos metodos: procedure Thome.PageDispatcherBeforeDispatchPage(Sender: TObject; const PageName: String; var Handled: Boolean); begin Inc (HitCount); end; procedure Thome.CountGetValue(Sender: Variant) ; begin

TObject; var Value:


Value : = Hitcount; end;

Tambien podriamos usar el nombre de la pagina para el recuento de 10s accesos a cada una de ellas (y podriamos aiiadir soporte para permanencia, ya que el contador se pone a cero cada vez que ejecutamos una nueva instancia de la aplicacion). Ahora que hemos aiiadido un campo personalizado a un adaptador ya existente, (correspondiente a1 objeto de guion Application), podemos acceder a1 mismo desde cualquier guion de la siguiente manera: <p>Application hits since last activation: < % = Application. C o u n t . V a l u e % X / p >

Componentes de adaptadores Del mismo modo, podemos aiiadir tambien adaptadores personalizados a paginas especificas. Si lo que necesitamos es pasar solamente unos cuantos campos, es mejor utilizar el componente Adapter generico. Otros adaptadores personalizados (ademas del ApplicationAdapter global que ya hemos utilizado) son:

El componente PagedAdapter: Tiene soporte interno para mostrar su contenido en diversas paginas. El componente DataSetAdapter: Se utiliza para acceder desde un guion a un conjunto de datos de Delphi y que veremos dentro de poco. El StringValuesList: Contiene una lista de pares nombrelvalor, en forma de lista de cadenas, que puede utilizarse directamente o para proporcionar una lista de valores a un campo de adaptador. El adaptador DataSetValueList heredado juega el mismo papel per0 obtiene la lista de pares nombrel valor de un conjunto de datos, proporcionando soporte para busquedas y otras selecciones. Los adaptadores relacionados con el usuario, como 10s adaptadores EndUser, Endusersession y LoginForm, que se utilizan para acceder a informacion de sesion y usuario, y para crear el formulario de entrada para la aplicacion, el cual esta ligado automaticamente con la lista de usuarios. Hablaremos de estos adaptadores tambien mas adelante.

Uso del Adapterpageproducer La mayoria de estos componentes se utilizan junto con un componente Adapter Page Producer. ~ s t puede e generar partes de un gui6n despuks de que hayamos diseiiado visualmente el resultado deseado. A mod0 de ejemplo, hemos aiiadido la pagina inout a la aplicacion WSnap2, que contiene un adaptador con dos campos, uno estandar y otro booleano: object Adapterl: TAdapter OnBeforeExecuteAction = AdapterlBeforeExecuteAction


o b j e c t TAdapterActions o b j e c t AddPlus: TAdapterAction OnExecute = AddPlusExecute end o b j e c t Post: TAdapterAction OnExecute = PostExecute end end o b j e c t TAdapterFields o b j e c t Text: TAdapterField OnGetValue = TextGetValue end object Auto: TAdapterBooleanField OnGetValue = AutoGetValue end end end

El adaptador tiene tambien un par de acciones utilizadas para enviar la entrada del usuario actual y para aiiadir un signo + a1 texto. El mismo signo se aiiade cuando activamos el campo Auto. Si utilizamos un codigo HTML basico, el desarrollo de la interfaz de usuario para este formulario y el guion relacionado con ella llevaria bastante tiempo. Pero el componente AdapterPageProducer (utilizado en esta pagina) tiene un diseiiador HTML integrado, que Borland llama Web Surface Designer. A1 utilizar esta herramienta, podemos aiiadir visualmente un formulario a la pagina HTML y aiiadirle un AdapterFieldGroup. Hay que conectar este grupo de campo a1 adaptador para que aparezcan automaticamente 10s editores para 10s dos campos. Despues podemos aiiadir un AdapterComrnandGroup y conectarlo a1 AdapterFieldGroup para conseguir botones para todas las acciones del adaptador. La figura 20.9 muestra un ejemplo de este diseiiador: Es decir, para ser mas precisos, 10s campos y 10s botones se muestran automaticamente si activamos las propiedades Add De f a u 1 t Fie 1 d s y AddDef aul tcommands de 10s grupos de campos y de ordenes. El efecto de estas operaciones visuales para construir este formulario queda resumido en el siguiente fragment0 de codigo DFM: object AdapterPageProducer: TAdapterPageProducer object AdapterForml: TAdapterForm object AdapterFieldGroupl: TAdapterFieldGroup Adapter = Adapter1 object FldText: TAdapterDisplayField FieldName = ' Text ' end object FldAuto: TAdapterDisplayField FieldName = 'Auto' end end object AdapterCornrnandGroupl: TAdapterCommandGroup


Displaycomponent = AdapterFieldGroupl object CmdPost: TAdapterActionButton ActionName = ' P o s t ' end object CmdAddPlus: TAdapterActionButton Act ionName = ' A d d P l u s ' end end end end

Figura 20.9. El Web Surface Designer para la pagina inout del ejernplo WSnap2, en tiernpo de disefio.

Ahora que tenemos una pagina HTML con algunos guiones para mover 10s datos a nuestro antojo y enviar ordenes, v e a m s el codigo fuente necesario para que funcione este ejemplo. En primer lugar, tenemos que aiiadir a la clase dos campos locales para almacenar 10s campos del adaptador y poder manipularlos. Tambien necesitamos implementar el evento OnGetValue para ambos, devolviendo 10s valores de campo. Cada vez que se hace clic sobre uno de 10s botones, tenemos que conseguir el texto que se ha pasado a1 usuario, que no se copia automaticamente en el campo correspondiente del adaptador. Podemos conseguir este efecto si nos fijamos en la propiedad Actionvalue de estos campos, la cual solamente se establece si escribimos algo (es por esto que, si no lo hacemos, tenemos que establecer el campo booleano como False). Para evitar la repeticion de este codigo para ambas acciones, lo colocaremos en el evento OnBeforeExecuteAction del modulo de la pagina Web. procedure Tinout.AdapterlBeforeExecuteAction(Sender, TOb j ect ; Params: TStrings; var Handled: Boolean);

Action:


begin i f Assigned (Text.ActionValue) then fText : = Text.ActionValue.Va1ues [O]; fAuto : = Assigned (Auto-Actionvalue); end;

Fijese en que cada accion puede tener varios valores (en caso de que 10s componentes permitan selection multiple); aunque como no es este el caso, simplemente tomaremos el primer elemento. Por ultimo, hemos escrito el siguiente codigo para 10s eventos O n E x e c u t e de las dos acciones: procedure Tinout.AddPlusExecute(Sender: TStrings) ; begin fText : = ÂŁText + ' + ' ; end: procedure Tinout.PostExecute(Sender: TStrings) ; begin i f ÂŁAuto then AddPlusExecute (Self, nil) ; end;

TObject;

TObject;

Params:

Params:

Como alternativa, 10s campos de 10s adaptadores tienen una propiedad EchoAc t ion Fie l d V a l u e publica que podemos establecer para obtener el valor introducido por el usuario y colocarlo en el formulario resultante. Esta tecnica se utiliza tipicamente en caso de errores, para permitir que el usuario modifique la entrada, partiendo de 10s valores ya introducidos.

I

hojas de estilo dn cascada ( ~ a s c a d i n ~ ~ Sheet, t y l e CSS). ~ e + p u e ddefinir e la CSS para una pagina utilizando ya sea ia propiedad S t y l e s F i l e o la lista cie cadena s t y l e s . Cualquier elemento del editor de 10s elementos del productor puede definir un estilo basico o escoger un estilo del CSS enlazado. Se realiza esta uItima operation (que es el enfoque que se sugiere) utilizando la propiedad S t y l e R u l e ) .

Guiones en lugar de codigo Incluso ese ejemplo del uso combinado de un adaptador y un productor de pagina de adaptador, con su diseiiador visual, muestra la potencia de esta arquitectura. Sin embargo, este enfoque solo tiene un inconveniente: al permitir que 10s componentes generen el guion (en el codigo HTML solo tenemos la etiqueta < #SERVE RSCR I PT>), nos ahorramos mucho tiempo de desarrollo, per0 acaba-


remos mezclando el guion con el codigo, de manera que 10s cambios en la interfaz de usuario requeriran actualizar el programa. Se pierde el reparto de responsabilidades entre el desarrollador de la aplicacion Delphi y el diseiiador de HTML y guiones. E, ironicamente, se acabara necesitando la ejecucion de un guion para realizar algo que el programa Delphi podria haber hecho de manera correcta y posiblemente a mayor velocidad. WebSnap es una arquitectura potente y un gran paso adelante con respecto a WebBroker, per0 deberia utilizarse con cuidado para evitar el ma1 uso de algunas de estas tecnologias ya que son simples y potentes. Por ejemplo, podria merecer la pena utilizar el diseiiador Adapterpageproducer para generar la primera version de una pagina, y despues coger el guion generado y copiarlo en el codigo HTML de un simple Pageproducer, de manera que un diseiiador Web pueda modificar el guion con una herramienta especifica. Para aplicaciones mas complicadas, es preferible usar las posibilidades que ofrecen XML y XSL, que se encuentran disponibles en esta arquitectura aunque no tengan un papel central. En el capitulo 22 hablaremos mas sobre este tema.

Encontrar archivos Cuando se ha escrito una aplicacion como la que acabamos de describir, hay que desplegarla como un CGI o como una biblioteca dinamica. En lugar de colocar las plantillas en 10s archivos incluidos en la misma carpeta que el archivo ejecutable, puede dedicarse una subcarpeta o carpeta personalizada para albergar todos 10s archivos. El componente LocateFileService se encarga de esta tarea. El componente no resulta intuitivo. En lugar de tener que especificar una carpeta destino como una propiedad, el sistema lanza uno de 10s eventos de este componente cada vez que tiene que encontrar un archivo. (Este enfoque es mucho mas potente.) Existen tres eventos: OnFindIncludeFile,OnFindStream y OnFindTempla te File.El primer y el ultimo evento devuelven el nombre del archivo a utilizar en un parametro var.El evento On Findst ream permite incluso proporcionar directamente un flujo, empleando uno de 10s que ya se encuentran en memoria o que se ha creado a1 vuelo, extraido de una base de datos, conseguido mediante una conexion HTTP o de cualquier otra manera que pueda pensarse. En el caso mas simple del evento OnFind Include File,se puede escribir un codigo como el siguiente: procedure TPageProducerPage2.LocateFileServicelFindIncludeFile( ASender: TObject; AComponent: TComponent; const AFileName: String; var AFoundFile: String; var AHandled: Boolean); begin AFoundFile : = DefaultFolder + AFileName; AHandled := True; end ;


WebSnap y bases de datos Una de las areas en las que mas destaca Delphi es en la programacion de bases de datos. Por ello, no es sorprendente que disponga de un amplio soporte para el manejo de conjuntos de datos dentro del marco WebSnap. Concretamente, podemos utilizar el componente DataSetAdapter para conectar con un conjunto de datos y mostrar sus valores en un fonnulario o tabla empleando el editor visual del componente AdapterPageProducer.

Un modulo de datos WebSnap Como ejemplo, hemos creado una nueva aplicacion WebSnap (llamada WSnapTable) con un AdapterPageProducer como pagina principal para que muestre una tabla y otro AdapterPageProducer en una pagina secundaria para mostrar un fonnulario con un unico registro. Tambien hemos aiiadido a la aplicacion un WebSnap Data Module como un contenedor de 10s componentes de conjunto de datos. El modulo de datos tiene un ClientDataSet enlazado a un conjunto de datos de dbExpress a traves de un proveedor y basado en una conexion InterBase, como se muestra a continuacion: o b j e c t ClientDataSetl: TClientDataSet Active = True ProviderName = ' D ataSetProviderll end o b j e c t SQLConnectionl: TSQLConnection Connected = True ConnectionName = ' IBLocal' LoginPrompt = False end o b j e c t SQLDataSet 1: TSQLDataSet SQLConnection = SQLConnectionl CommandText = ' select CUST-NO, CUSTOMER, ADDRESS-LINEI, STATE-PROVINCE, ' + ' COUNTRY from CUSTOMER' end o b j e c t DataSetProviderl: TDataSetProvider DataSet = SQLDataSetl end

CITY,

El DataSetAdapter Ahora que tenemos disponible un conjunto de datos, podemos aiiadir a la primera pagina un DataSetAdapter y conectarlo a1 ClientDataSet del modulo Web. Automaticamente, el adaptador hace que todos 10s campos del conjunto de datos y diversas acciones predetenninadas para operar con el conjunto (corno Delete,


Edit y Apply) esten disponibles. Podemos aiiadirlos explicitamente a las colecciones A c t i o n s y F i e l d s para excluir algunos y personalizar su comportamiento, per0 no siempre es necesario. A1 igual que el PagedAdapter, el DataSetAdapter tiene una propiedad P a g e s i z e que podemos utilizar para indicar el numero de elementos que queremos mostrar en una pagina. El componente tambien dispone de metodos que podemos utilizar para recorrer las paginas. Este enfoque es muy aconsejable para visualizar un gran conjunto de datos dentro de una cuadricula. A continuacion veremos 10s valores del adaptador para la pagina principal del ejemplo WSnapTable: object DataSetAdapterl: TDataSetAdapter DataSet = WebDataModulel.ClientDataSet1 Pagesize = 6 end

El productor de pagina correspondiente tiene un formulario que contiene dos grupos de ordenes y una cuadricula. El primer grupo (que se muestra por encima de la cuadricula) tiene las siguientes ordenes predefinidas para manipular paginas: CmdPrevPage, CmdNextPage y CmdGotoPage. Esta ultima genera una lista de numeros para las paginas, para que de esta forma el usuario pueda acceder directamente a cada una de ellas. El componente AdapterGrid contiene las columnas predeteminadas y una mas que alberga 10s ordenes de edicion ( E d i t ) y borrado ( D e l e t e ) . El grupo de ordenes inferior tiene un boton que se utiliza para crear un nuevo registro. La figura 20.10 muestra un ejemplo del aspect0 de la tabla y la configuracion completa del AdapterPageProducer se muestra en el listado 20.2. Listado 20.2. La configuracion del AdapterPageProducer para la pagina principal de WSnapTable. o b j e c t AdapterPageProducer: TAdapterPageProducer object AdapterForml: TAdapterForm object AdapterCommandGroupl: TAdapterCommandGroup Displaycomponent = AdapterGridl object CmdPrevPage: TAdapterActionButton ActionName = ' P r e v P a g e ' Caption = ' P r e v i o u s P a g e ' end object CmdGotoPage: TAdapterActionButton ... object CmdNextPage: TAdapterActionButton ... Act ionName = ' N e x t P a g e ' Caption = ' N e x t P a g e ' end end object AdapterGridl: TAdapterGrid TableAttributes.Cel1Spacing = 0 TableAttributes.Cel1Padding = 3 Adapter = DataSetAdapterl AdapterMode = ' B r o w s e '


object ColCUST-NO: TAdapterDisplayColumn

...

object AdapterCommandColumnl: TAdapterCommandColumn Caption = 'COMMANDS' object CmdEditRow: TAdapterActionButton ActionName = 'EditRow' Caption = 'Edlt' PageName = 'formview' DisplayType = ctAnchor end object CrndDeleteRow: TAdapterActionButton ActionName = 'DeleteRowt Caption = 'Delete' DisplayType = ctAnchor end end end object AdapterCommandGroup2: TAdapterCommandGroup Displaycomponent = AdapterGridl object CmdNewRow: TAdapterActionButton Act ionName = ' NewRow' Caption = 'New' PageName = ' formview' end end end end

\\'SnapTa ble table Prenous Page 1 2

3 Ntxt Pme

L

Figura 20.10. La pagma que rnuestra el ejemplo WSnapTable al inicio incluye la parte in~cialde una tabla paginada.


En este listado hay algunas cosas que debemos de tener en cuenta. La primera es que la cuadricula tiene la propiedad AdapterMode establecida como Browse. Otras posibilidades podrian ser Edit, Insert y Query. Este mod0 de representacion del conjunto de datos para adaptadores determina el tipo de interfaz de usuario (texto, cuadros de dialog0 y otros controles de entrada) y la visibilidad de otros botones (como por ejemplo 10s botones Apply y Cancel que solo estan presentes en la vista de edicion; lo contrario sucede con la orden Edit). -. ---NOTA: ~ambi6npucdc modificarse el m o d ~del adaptador mediante gguio-

nes de senridor y accediendo a Adapter

o ode .

En segundo lugar, hemos modificado la forma de representar las ordenes en la cuadricula usando el valor c tAnchor para la propiedad Di splayT ype, en lugar de utilizar el boton de estilo predeterminado. En la mayoria de 10s componentes de esta arquitectura encontraremos propiedades similares, que nos permiten ajustar el codigo HTML que producen.

Edicion de 10s datos en un formulario Algunas de las ordenes estan conectadas a distintas paginas, que se mostrara cuando se invoquen esas ordenes. Por ejemplo, la propiedad PageName de la orden edit esta establecida como formview. Esta segunda pagina de la aplicacion dispone de un AdapterPageProducer con componentes enganchados a1 DataSetAdapter de la otra tabla, de forma que todas las peticiones se sincronicen automaticamente. De hecho, si hacemos clic sobre la orden Edit, el programa abrira una pagina secundaria mostrando 10s datos del registro correspondiente a la orden. El listado 20.3 nos muestra 10s detalles del productor de la segunda pagina del programa. Crear visualmente el formulario HTML, utilizando el diseiiador especifico de Delphi (vease la figura 20.1 l), hace que esta operacion sea muy rapida. Listado 20.3. La configuracidn del AdapterPageProducer para la phgina formview. object AdapterPageProducer: TAdapterPageProducer object AdapterForml: TAdapterForm object AdapterErrorListl: TAdapterErrorList Adapter = table.DataSetAdapter1 end object AdapterCommandGroupl: TAdapterCommandGroup Displaycomponent = AdapterFieldGroupl object CmdApply: TAdapterActionButton ActionName = ' A p p l y 1 PageName = ' table1 end object CmdCancel: TAdapterActionButton


ActionName = ' C a n c e l ' PageName = ' t a b l e ' end object CmdDeleteRow: TAdapterActionButton ActionName = ' D e l e t e R o w l Caption = ' D e l e t e ' PageName = ' t a b l e 1 end end object AdapterFieldGroupl: TAdapterFieldGroup Adapter = table.DataSetAdapter1 AdapterMode = ' E d i t ' object FldCUST-NO: TAdapterDisplayField Displaywidth = 10 FieldName = ' CUST-NO' end object FldCUSTOMER: TAdapterDisplayField end end end

-iJp1

-

+

-

-

--

-

--

-

- ---. - -- .--

- --

--

AdaplmPagcRoduar AdaptetForrnl AdaplerFolrnl AdaplerCornmar AdapIetFreldGro

E I

1

@fowsw HTML Scrlpl

Apply

I

Cancel

I

I Delete (

CUST-NO pE7S~gnatureDes~gn CXTsTOhE.R ~c Bivd ADDRESS-LINE1 15500 P a c ~ f He~ghts San D~ego CFY STATE_PRO\ WCE CA USA COUNTRY

L A

Q 2l

Figura 20.11. La pag~naforrnv~ewmostrada por el ejernplo WSnapTable en t~ernpo de ejecuc~on,en el Web Surface Des~gner(o el ed~torde Adapterpageproducer).

En el listado, puede verse que todas las operaciones envian el usuario de vuelta a la pagina principal y que el AdapterMode se establece como Edit, a menos que haya conflictos o errores en la actualizacion. En este caso se vuelve a mostrar


la misma pagina, con una descripcion de 10s errores a1 afiadir un componente A d a p t e r E r r o r L i s t en la parte superior de la lista. La segunda pagina no se publica, porque seleccionarla sin hacer referencia a un registro especifico no tendria sentido. Para no publicar la pagina, hay que comentar el indicador correspondiente en el codigo de inicializacion. Finalmente, para hacer que 10s cambios en el conjunto de datos Sean persistentes, podemos llamar a1 metodo A p p l y u p d a t e s en 10s eventos O n A f t e r P o s t y O n A f t e r D e l e t e del componente C l i e n t D a t a S e t que esta en el modulo de datos. Otro problema (el cual no hemos arreglado) tiene que ver con el hecho de que el servidor SQL asigne un ID a cada cliente, de manera que cada vez que introducimos un nuevo registro, no se alinean 10s datos en el ClientDataSet y en la base de datos. Esto puede causar errores de tipo "Record Not Found" que indican que no se encuentra un registro, por este problema de desalineamiento.

El componente DataSetAdapter tiene un soporte especifico para las relaciones maestroldetalle entre 10s conjuntos de datos. Despues de crear la relacion entre 10s conjuntos de datos, como siempre, definimos un adaptador para cada uno de ellos y, a continuation, conectamos la propiedad M a s t e r A d a p t e r del adaptador del conjunto de 10s datos de detalle. A1 establecer la relacion maestroldetalle entre 10s adaptadores hacemos que estos trabajen de una forma mas fluida. Por ejemplo, cuando cambiamos el mod0 de trabajo del conjunto maestro, o introducimos nuevos registros, el conjunto de detalle pasa automaticamente a1 mod0 de edicion o se actualiza. El ejemplo WSnapMD utiliza un modulo de datos para definir una relacion de ese tipo. Incluye dos componentes c 1i e n t D a t ase t ,cada uno de ellos conectado a un SQLDataSet mediante un proveedor. Cada componente de acceso a datos se refiere a una tabla, y 10s componentes C l i e n t D a t aSe t definen una relacion maestroldetalle. El mismo modulo de datos contiene dos adaptadores de conjunto de datos que se refieren a 10s dos conjuntos y siguen definiendo dicha relacion: object dsaDepartment: TDataSetAdapter DataSet = cdsDepartment end object dsaEmployee: TDataSetAdapter DataSet = cdsEmployee MasterAdapter = dsaDepartment end


fallo cierra el conjunto de datos en cada interaction, con lo que se pierde information de estado. La unica pagina de esta aplicacion WebSnap tiene un componente AdapterPageProducer conectado a ambos adaptadores. El formulario de esta pagina tiene un grupo de campos enganchado a1 maestro y una cuadricula conectada con el detalle. A diferencia de otros e.jemplos, hemos tratado de me.jorar la interfaz de usuario aiiadiendo atributos personalizados para diversos elementos. Hemos usado un fondo gris, mostrado algunos bordes de cuadricula (Web Surface Designer suele usar cuadriculas HTML), centrado la mayoria de 10s elementos y aiiadido espacios. Fijese en que hemos aiiadido espacios adicionales a 10s titulos de boton para impedir que Sean demasiado pequeiios. Puede verse el codigo relacionado en el siguiente fragment0 detallado y su efecto en la figura 20.12: object AdapterPageProducer: TAdapterPageProducer object AdapterForrnl: TAdapterForrn Custom = 'Border="lU CellSpacing="OrrCellPadding="lO"

' BgColor="Silver"

align="center"'

object AdapterCommandGroupl: TAdapterCommandGroup Displaycomponent = AdapterFieldGroupl Custom = 'Align="Center"' object CmdFirstRow: TAdapterActionButton . . . object CmdPrevRow: TAdapterActionButton ... object CmdNextRow: TAdapterActionButton . . . object CmdLastRow: TAdapterActionButton ... end object AdapterFieldGroupl: TAdapterFieldGroup Custom = ' BgColor="Silver " ' Adapter = WDataMod.dsaDepartment AdapterMode = ' Browse' end object AdapterGridl: TAdapterGrid TableAttributes.BgCo1or = 'Silver' TableAttributes.CellSpacing = 0 TableAttributes.CellPadding = 3 HeadingAttributes. BgColor = ' Gray' Adapter = WDataMod.dsaEmployee AdapterMode = 'Browse' object ColEMP-NO: TAdapterDisplayColumn ... object ColFIRST-NAME: TAdapterDisplayColumn object ColLAST-NAME: TAdapterDisplayColumn ... object ColDEPT-NO: TAdapterDisplayColumn . . . object ColJOB-CODE: TAdapterDisplayColumn ... object ColJOB-COUNTRY: TAdapterDisplayColumn ... object ColSALARY: TAdapterDisplayColumn ... end end end

...

' +


First

(

Revtous

(

Nexl

(

Lns!

I

DEPARTMENT Corporate Headquarters DEPT-NO 000 m - D m LOCATION M a t m y BUDGET 1000000

Tm

Lee

000

' A h

USA

53733

Bender

000

CEO

/USA

,212850

I

1&3'sf0

-d'.

q .

Figura 20.12. El ejemplo WSnapMD muestra una estructura maestroldetalle y tiene una representacion personalizada.

Sesiones, usuarios y permisos Otra interesante caracteristica de la arquitectura WebSnap es su soporte para sesiones y usuarios. Las sesiones se soportan utilizando un enfoque clasico: cookres temporales. Estas cookies se envian a1 navegador para que sucesivas peticiones del mismo usuario puedan ser reconocidas por el sistema. A1 aiiadir datos a una sesion en lugar de a1 adaptador de una aplicacion, se puede disponer de datos que dependan de la sesion o el usuario especifico (aunque un usuario puede ejecutar varias sesiones abriendo multiples ventanas de navegacion en el mismo ordenador). Para soportar las sesiones, la aplicacion mantiene 10s datos en memoria, por lo que esta caracteristica no esta disponible en programas CGI.

Uso de sesiones Para subrayar la importancia de este tip0 de soporte, hemos creado una aplicacion de WebSnap con una sola pagina que muestra tanto el numero total de visitas como el numero total de visitas para cada sesion. El programa tiene un componente Sessionservice con valores predeterminados para sus propiedades MaxSess ions y Def aultTimeout . Para cada nueva peticion, el programa incrementa tanto su campo privado nHits del modulo de pagina como el valor SessionHits de la sesion actual:


procedure TSessionDemo.WebAppPageModuleBeforeDispatchPage(Sender: TOb j ect; const PageName: String; var Handled: Boolean) ; begin // incrementa las visitas d e aplicacion y sesion Inc (nHits); WebContext.Session.Values ['SessionHitsl] : = Integer ( ~ e b ~ ~ n t.Session.Values ext [ 'SessionHits '1 ) + 1 ; end:

NOTA: El objeto Webcontext (de tipo ~ ~ e b ~ o n t e x estuna ) variable de hebra creada por Websnap para cada'peticih. Proporciona un a c a so seguro fiente a hebras a otras d k s globales usadas por el programa. El codigo HTML asociado muestra inforrnacion de estado utilizando tanto etiquetas personalizadas evaluadas por el evento OnTag del productor de pagina como el guion evaluado por el motor. Esta es la parte mas importante del archivo HTML: <h3>Plain Tags</h3> <p>Session id: <#SessionID> <br>Session hits : < # S e s s i o n ~ its></p> <h3>Script</h3> <p>Session hits (via application) : <%=Application. SessionHi ts. Val ue%> <br>Application hits: <%=Application.Hits.Value%></p>

Los parametros de la salida 10s proporciona el controlador del evento OnTag y 10s eventos OnGetValue de 10s campos: procedure TSessionDemo.PageProducerHTMLTag(Sender: TObject; Tag: TTag; const TagString: String; Tagparams: TStrings; var ReplaceText : String) ; begin if TagString = 'SessionID' then ReplaceText : = WebContext.Session.Session1D else if TagString = 'SessionHitsl then ReplaceText : = WebContext.Session.Va1ues ['SessionHits'] end; procedure TSessionDemo.HitsGetValue(Sender: Variant) ; begin Value : = nHits; end;

TObject; var Value:

procedure TSessionDemo.SessionHitsGetValue(Sender: TObject; var Value : Variant) ;


begin Value : = I n t e g e r [ 'SessionHit s ' 1 ) ; end;

(WebContext.Session.Va1ues

El efecto de este programa se muestra en la figura 20.13, donde hemos activado dos sesiones en dos navegadores distintos. - .

-

TRUCO:En este ejemplo, hemos usado la sustitucih de etiquetas tradicional de WebBroker y 10s nuevos campos de adaptador y guiones de WebSnap, para que se puedan comparar 10s dos enfoques. Hay que tener presente que arnbos se encuentran disponibles en una aplicacion de WebSnap.

P I a h TRy Scrr:an td CY[ZuGcM3zRx85F Scmon h r 6

Script Snnonhts (ma npphcannn) 6 Appbcabon hllr 12

Figura 20.13. Dos instancias del navegador funcionan con dos sesiones distintas de la misrna aplicacion de WebSnap.

Peticion de entrada en el sistema Ademas de sesiones genericas, WebSnap tambien tiene un soporte especifico para usuarios y sesiones de autorizacion basada en entradas en el sistema. Se puede afiadir a una aplicacion una lista de usuarios (con el componente WebUserList), cada uno con un nombre y una contraseiia. Este componente es bastante basico en cuanto a 10s datos que puede contener. No obstante, en lugar de rellenarlo con una lista de usuarios, se puede mantener la lista en una tabla de base de datos (o en otro formato propietario) y utilizar 10s eventos del componente WebUserList para recuperar 10s datos personalizados de usuario .p comprobar sus contraseiias. En general tambien se aiiadiran a la aplicacion 10s componentes SessionService y EndUserSessionAdapter. En este punto, puede pedirse a1 usuario que se registre, indicando para cada pagina si puede ser vista por cualquiera o solo por usua-


rios registrados. Esto se lleva a cab0 activando el indicador wpLoginRequired en el constructor de las clases TWebPageModuleFactory y TWebAppPageModuleFactory en el codigo de inicializacion de la unidad de la pagina Web. -

--

NOTA: Los derechos y la publicacion de information

.

.

se incluyen en la

fabric:a en lugar de en el WebPageModule ya que el programa puede cornu 10s derechos de acceso y listar las paginas sin cargar siquiera el I-

Cuando un usuario trata de ver una pagina que requiera la identificacion del usuario. aparecera la pagina de entrada en el sistema indicada en el componente EndUserSessionAdapter. Se puede crear una pagina de este tip0 facilmente, creando un nuevo modulo de pagina Web basado en un Adapterpageproducer y aiiadiendole el LoginFormAdapter. En el editor de la pagina, se aiiade un grupo de campos dentro de un formulario, se conecta el grupo de campos a1 LoginFormAdapter, y se aiiade un grupo de comandos con el boton predeterminado Login. El formulario de entrada resultante tendra campos para el nombre de usuario y su contraseiia, y tambien para la pagina solicitada. Este ultimo valor se rellena automaticamente con la pagina solicitada, en caso de que la pagina necesitara autorizacion y el usuario no haya entrado en el sistema aun. De este modo, un usuario puede alcanzar inmediatamente la pagina solicitada sin tener que volver a1 menu general. El formulario de entrada tipicamente es no publicado, porque la orden Login correspondiente ya esta disponible cuando el usuario no se encuentra dentro del sistema; cuando entra el usuario, se sustituye con un comando Logout (de salida del sistema). Este comando lo obtiene el guion estandar del modulo de la pagina Web, en particular con el siguiente codigo: i f

( E n d U s e r . L o g o u t != n u l l ) ( %> i f ( E n d U s e r . D i s p l a y N a m e != ' ') ( %>

<hl>Welcome <%=EndUser.DisplayName %> i f ( E n d U s e r . L o g o ~ r tE n a b l e d )

}

.

:></hl>

( %>

< a href="<%=EndUser.Logout.AsHREF%>">Logout</a>

I i f

%> ( E n d U s e r . LoginPorrn. E n a b l e d )

( %>

< a href="<%=EndUser.LoginForm.As~R~FG>">Login</a> }

} %> %>

No hay mucho mas que decir sobre la aplicacion WSnapUsers, ya que casi no tiene codigo ni valores personalizados. Este guion para la plantilla esthdar muestra como se realiza el acceso a 10s datos de usuario.


Derechos de acceso a una unica pagina Ademas de hacer que las paginas necesiten entrar en el sistema para acceder a ellas, se puede dar a usuarios especificos el derecho de ver mas paginas que otros. Cualquier usuario tiene un conjunto de derechos separados por punto y coma, o comas. El usuario debe tener definidos todos 10s derechos para la pagina solicitada. Estos derechos, que en general se listan en las propiedades ViewAccess y Modif yAccess de 10s adaptadores, indican respectivamente si el usuario puede ver 10s elementos dados mientras navega o si puede editarlos. Estas configuraciones son muy granulares y pueden aplicarse a adaptadores completos o a campos especificos de adaptadores (fijese en que me estoy refiriendo a campos de adaptadores, no a 10s componentes de la interfaz de usuario del diseiiador). Por ejemplo, pueden esconderse algunas columnas de una tabla para unos usuarios escondiendo 10s campos correspondientes (y tambien en otros casos, tal y como especifique la propiedad HideOptions). El componente global PageDispatcher tambien tiene eventos oncanviewpage y OnPageAcce ssdenied, que puede utilizarse para controlar el acceso a varias paginas de un programa dentro del codigo del programa, proporcionando un control mucho mayor.


Programacion Web con

Desde 10s dias de Delphi 2, Chad Z . Hower se ha encargado de la creacion de una arquitectura Delphi que simplifique el desarrollo de aplicaciones Web, con la idea de hacer que la programacion Web tan simple y visual como la programacion de formularios Delphi estandar. Algunos programadores estan completamente acostumbrados a las tecnologias HTML, JavaScript, hojas de estilo en cascada y las mas recientes tecnologias de Internet. Otros programadores simplemente quieren crear aplicaciones Web en Delphi del mismo mod0 en que crean aplicaciones VCL 0 CLX. IntraWeb esta pensada para este segundo tipo de desarrolladores, aunque es tan potente que, incluso programadores expertos en Web pueden sacar partido de su utilizacion. Segun Chad, IntraWeb esta pensado desarrollar aplicaciones Web, no sitios Web. Aun mas, 10s componentes de IntraWeb pueden usarse en una aplicacion especifica o pueden utilizarse en un programa de WebBroker o WebSnap. En este capitulo no hablaremos en detalle de IntraWeb, ya que es una biblioteca muy grande, como 50 componentes instalados en la paleta de Delphi y varios diseiiadores de modulos. Lo que vamos a hacer es comentar su base, de manera que pueda sopesarse su uso para futuros proyectos o para partes de estos proyectos, lo que resulte mas adecuado.


' I I

\

-

I)

TRUCO: Si se desea conseguir documentation sobre IntraWeb, se pueden consultar 10s manuales PDF que se encuentran en el Companion CD de Delphi 7. Si no pueden encontrarse aqui, tambidn se pueden descargar desde el sitio Web de Atozed Software. Para soporte sobre IntraWeb, es conveniente acudir a 10s grupos de noticias de Borland.

NOTA: Este capitulo ha sido especialmente revisado por Chad Z. Hower (tambien conocido como "Kudzu", el autor y coordinador de proyecto original de Internet Direct (Indy) y autor de IntraWeb. Entre las especialidades de Chad se incluyen las redes y programacion TCPIIP, la comunicacion entre procesos, la programacion distribuida, 10s protocolos de Lnternet y la programacion orientada a objetos. Cuando no esta programando, tambiin le gusta montar en bicicleta, kayak, escalar, descender en ski, conducir y hacer casi cualquier cosa a1 aire libre. Chad tambien publica articulos, programas y herramientas gratuitas (y otras curiosidades) en Kudzu World en el URL http:llwww.Hower.o~Kudrul. Chad es un estadounidense expatriado que pasa sus veranos en San Petersburgo (Rusia) y sus inviernos en Limassol (Chipre). Se puede contactar con Chad a traves de cpub@Hower.org. En este capitulo trataremos 10s siguientes temas: IntraWeb, aplicaciones Web y sitios Web. Uso de componentes IntraWeb. lntegracion con WebBroker y WebSnap. Aplicaciones Web de bases de datos. Uso de componentes de cliente.

lntroduccion a IntraWeb lntraWeb es una biblioteca de componentes creada por Atozed Software (www.atozedsoftware.com).En las ediciones Professional y Enterprise de Delphi 7, sc puede encontrar la version correspondiente de IntraWeb. La version Professional solo puede usarse en mod0 de pagina, como veremos mas adelante. Aunque Delphi 7 es la primera version del IDE de Borland en incluir este con-junto de componentes, IntraWeb lleva existiendo ya varios aiios, ha sido muy bien recibida y apoyada, con la disponibilidad afiadida de varios componentes de terceras partes.


-

I

-

TRUC0 :Entre 10s componentes de terceras partes parar IntraWeb se inclu yen IW'Char de Steema (10s creaclores de Teechart), l7N Bold de CentillelY .. - - -. -- - -

(para la 1ntegraci6n con Bold), IWOpenSource, IWTranslator, IWD~alogs, IWDataModulePool d e Arcana, IW Component Pack d e T M S y IWGranPrimo de GranPrimo. Se puede encontrar una lista actualizada de este tip0 de componentes en www.atozedsoftware.com.

Aunquc no se dispone del codigo fuente para la biblioteca central (disponible bajo solicitud y previo pago), la arquitectura IntraWeb es bastante abierta, y el codigo fuente completo dc 10s componentes esta plenamente disponible. IntraWeb forma ahora parte de la instalacion estandar de Delphi, per0 tambien esta disponible para Kylis. Si se escriben con cuidado, las aplicaciones IntraWeb pueden ser completamente multiplataforma.

siones para C++ Builder y Java. Se esta trabajando en una version .NET y probablemente estara disponible junto con una futura version de Delphi

Como propietario de Delphi 7, se puede recibir la primera publicacion de una actualizacion significativa (la vcrsion 5. I) y se pucde actualizar la licencia a una version completa de la edicion Enterprise de IntraWeb que incluye actualizaciones y soporte de Atozed Software (como puede comprobarse en su sitio Web). Si se desea una documentacion mas completa (archives de ayuda y PDF), es recomendable acceder a esta actualizacion a la version 5.1.

De sitios Web a aplicaciones Web Como ya se ha comentado, la idea que esconde IntraWeb es crcar aplicaciones Web en lugar de sitios Web. Cuando se trabaja con WebBroker o WebSnap (de 10s que se hablaba en el capitulo anterior), se puede pensar en terminos de paginas Web y productores de paginas, y trabajar muy cerca del nivel de la generation de paginas HTML. Cuando se trabaja con IntraWeb hay que pensar en terminos de componentes, sus propiedades y sus controladores de eventos, a1 igual que en el desarrollo visual con Delphi. Por ejemplo, se crea una nueva aplicacion IntraWeb mediante el menu File>New>Other, accediendo a la pagina IntraWeb del cuadro de dialogo New Items y escogiendo la opcion Stand Alone Application. En el cuadro de dialogo siguiente (que forma parte de Delphi, no del asistente de IntraWeb) se puede escoger una carpeta ya esistente o escribir el nombre de una que se creara nueva (lo comentamos porque no quedara muy claro en el dialogo). El programa resultante tiene un archivo de proyecto y dos unidades distintas.


Por el momento, creemos un ejemplo (llamado IWSimpleApp en el codigo fuente del libro). Para construirlo, habra que seguir estos pasos: 1. Accedemos a1 formulario principal del programa y le aiiadimos un boton, un cuadro de edicion y un cuadro de lista desde la pagina IW Standard de la paleta de componentes, Es decir, no aiiadimos 10s componentes VCL de la pagina Standard de la paleta, sino que utilizaremos 10s correspondientes componentes IntraWeb: IWButton, IWEdit y IWListbox. 2. Modificaremos ligeramente sus propiedades de esta manera: object IWButtonl: TIWButton Caption = 'Add I t e m ' end object IWEditl: TIWEdit Text = ' f o u r ' end object IWListboxl: TIWListbox 1tems.Strings = ( 'one ' 'two' ' t h r e e ') end

3. Controlamos el evento O n c l i c k del boton haciendo doble clic sobre el componente en tiempo de diseiio (como siempre) y escribiendo este codigo tan familiar: procedure TforrnMain.IWButtonlClick(Sender: begin 1WListBoxl.Items.Add (1WEditl.Text); end;

TObject);

Esto es todo lo que se necesita para crear una aplicacion basada en Web capaz de aiiadir texto a un cuadro de lista, como muestra la figura 2 1.1 (que muestra la version final del programa, con un par mas de botones). Lo que es importante tener en cuenta es cuando se ejecuta este programa es que cada vez que se hace clic sobre el boton, el navegador envia una peticion nueva a la aplicacion, que ejecuta el controlador de evento de Delphi y produce una nueva pagina HTML basada en el nuevo estado de 10s componentes del formulario. Cuando se ejecute la aplicacion, no se vera la salida del programa en el navegador, sino en el formulario del controlador de IntraWeb que muestra la figura 21.2. Una aplicacion IntraWeb independiente es un servidor HTTP completo, como se vera a continuacion. El formulario que se puede ver esta controlado por la llamada IWRun en el archivo de proyecto creado de manera predefinida en cada aplicacion IntraWeb independiente. El formulario de depuracion permite escoger un navegador y ejecutar la aplicacion a traves de el o copiar el URL de la aplicacion en el Portapapeles, para que se pueda pegar despues dentro del navegador. Es importante saber que la aplicacion utilizara de manera predetermi-


nada un numero de puerto aleatorio, que es distinto para cada ejecucion. Por eso habra que utiliza un URL distinta cada vez. Se puede modificar este comportamiento si se selecciona el diseiiador del controlador del servidor (que es parecido a un modulo de datos) y se fija la propiedad port. En el ejemplo hemos usado 8080 (uno de 10s puertos HTTP habituales), per0 otros valores tambien pueden funcionar.

one hvo

lhree lour four

Figura 21.1. El programa IWSimpleApp en un navegador

Fie

Run I.ldp

3wmi- 1 -Ia lnlraweb Version. 5 0 43

HTTP Pmt.8080

A

Packaged Enterprise L~censeNumber 0

.-. .-. ...............

--

-

-

II ,,,

Figura 21.2. El formulario de controlador de una aplicacion IntraWeb independiente.


El codigo IntraWeb es bbicamente codigo de servidor, pero IntraWeb tambien puede generar JavaScript para controlar algunas de las caracteristicas de la aplicacion. Tambien puedc e.jecutarse codigo adicional en el lado del cliente. Para haccr esto se utilizan componente especificos de cliente de IntraWeb o se escribe un codigo JavaScript personalizado. Como comparacion, 10s dos botones de la parte inferior del formulario en el ejemplo IWSimpleApp muestran un cuadro de mensaje utilizando dos tecnicas distintas. El primer0 de 10s dos botones (IWButton2) muestra un mensa.je mediante un cvento de servidor, con este codigo Delphi: procedure T f o r m M a i n . 1 ~ B u t t o n 2 C l i c k ( S e n d e r : TObject); var nItem: Integer; begin nItem : = 1WListboxl.ItemIndex; if nItem >= 0 then WebApp1ication.ShowMessage (1WListBoxl.Items [nItem]) else WebApplication.ShowMessage ('No ltem s e l e c t e d ' ) ; end;

El segundo de estos dos botones (IWButton3) utiliza JavaScript, que se inserta cn el programa Delphi preparando el controlador de eventos JavaScript apropjado en cl cditor especial de propiedades para la propiedad S c r i p t E v e n t s :

Un primer vistazo interior Ya se ha visto que crear una aplicacion IntraWeb es tan sencillo como crear un programa Delphi basado en formularies: se colocan componentes en un formulario y se controlan sus eventos. El efecto es bastante distinto, por supuesto, ya que la aplicacion se ejecuta en un navegador. Para comprender mejor lo que sucede, vamos a analizar el comportamiento interno de este programa tan sencillo. Esto deberia ayudar a comprender el efecto de establecer las propiedades de componente y trabajar con ellas en general. Se trata de un programa basado en un navegador, por lo que no hay mod0 mejor de comprenderlo que fijarse en el codigo HTML que envia a dicho navegador. Si abrimos el codigo de la pagina del programa IWSimpleApp (que no se muestra


aqui, porque ocuparia un espacio excesivo), se podra ver que esta dividido en tres secciones principales. La primera es una lista de estilos (basados en la etiqueta HTTP style) con lineas como la siguiente:

IntraWeb utiliza estilos para determinar no solo la apariencia visual de cada componente, como su fuente y color, sin0 tambien la posicion del componente, mediante el posicionamiento absoluto predetern~inado.Cada estilo se ve afectado por un cierto ni~merode propiedades de 10s componentes IntraWeb, asi que se puede experimentar sin problemas si se sabe algo de ho-jas de estilo. Si no se esta familiarizado con las ho-ias de estilo, lo mas facil es utilizar simplemente las propiedades y confiar en que IntraWeb hara lo mejor que pucda para representar 10s componentes en la pagina Web. El segundo bloque consiste cn codigo de guiones JavaScript. El bloque de guiones principal contiene el codigo de inicializacion y el codigo de 10s controladores de evcntos de cliente para 10s componentes, como el estracto siguiente: function IWBUTTONl-OnClick(ASender) ( r e t u r n SubmitClickConf irm( 'IWBUTTONI

', ' ', true, ' ' )

;

)

Este controlador llama a1 correspondiente codigo de servidor. Si se ha proporcionado directamente el codigo JavaScript en la aplicacion IntraWeb, como ya hemos comentado, se vera este codigo: f u n c t i o n IWBUTTON3_onClick(ASender)

(

window. alert (ASender.value) : )

La seccion de guiones de la pagina tambien hacer referencia a otros archivos necesarios para el navegador y que IntraWeb pone a su disposicion. Algunos de estos archivos son genericos: otros estan enlazados con un navegador especifico: IntraWeb detecta el navegador que se esta utilizando y devuelve un codigo JavaScript y archivos basicos JavaScript distintos. . -

-

- .

- . --

--

..-

- ..

NOTA: Ya que el lenguaje JavaScript no es idkntico para todos 10s navegadores, IntraWeb soporta solo algunos de ellos, como el de las ultimas versiones de Microsoft Internet Explorer, Netscape Navigator y Mozilla (un proyecto de codigo abierto usado durante la elaboration de este capitulo). Opera ofiece un soporte mas limitado de JavaScript, por lo que, de manera predeterminada, IntraWeb emitira un error si lo reconoce (de acuerdo con la propiedad SupportedBrowsers del controlador). Opera puede


por la version 5.1 de IntraWeb. Hay que tener presente que un navegador puede simular su identidad: Por ejemplo, es habitual que Opera este configurado para identificarse como Internet Explorer, lo que irnpedira una identificacion correcta para posibilitar el uso de sitios restringidos a otros navegadores, pero posiblemente llevara a errores en tiempo de ejecucion o inconsistencias. La tercera parte del HTML generado es la definition de la estructura de pagina. Dentro de la etiqueta body se encuentra una etiqueta f o r m (en la misma linea) con la siguiente accion de ejecucion:

La etiqueta f o r m contiene componentes especificos de la interfaz de usuario, como botones y cuadros de edicion: <input type="TEXTV name="IWEDITl" size="17" value="fourl' id="IWEDITlW class="IWEDITlCSS"> < i n p u t v a l u e = " A d d I t e m " name=" I W B U T T O N l " t y p e = " b u t t o n " o n c l i c k = " r e t u r n IWBUTTONl-OnClick(this);" id="IWBUTTONl" class="IWBUTTONlCSS">

El formulario tiene tambien algunos componentes ocultos que IntraWeb utiliza para llevar informacion entre el cliente y el servidor, Sin embargo, el URL es el mod0 mas importante de pasar informacion en IntraWeb; en el programa tendra este aspecto:

La primera parte es la direccion IP y el puerto que suelen utilizarse para la aplicacion IntraWeb independiente (cambia cuando se usa una arquitectura distinta para desplegar el programa), seguida del comando EXEC, un numero de creciente peticion y un identificador de sesion. Ya hablaremos mas tarde de la sesion, pero por ahora bastara con saber que IntraWeb utiliza un elemento del URL en lugar de cookies para permitir el acceso a sus aplicaciones a pesar de las posibles configuraciones de 10s navegadores. Si se prefiere, se pueden utilizar cookies en lugar del URL, modificando la propiedad TrackMode en el controlador del servidor.


Arquitecturas IntraWeb Antes de escribir mas ejemplos para mostrar el uso de otros componentes IntraWeb disponibles en Delphi 7, vamos a analizar otro elemento clave de IntraWeb: las distintas arquitecturas que pueden usarse para crear y desplegar aplicaciones basadas en esta biblioteca. Se pueden crear proyectos IntraWeb en el mod0 Application (donde son aplicables todas las caracteristicas de IntraWeb o en el mod0 Page (que es una version simplificada que puede aiiadirse a aplicaciones WebBroker o WebSnap ya existentes). Las aplicaciones que utilizan el mod0 Application pueden desplegarse como bibliotecas ISAPI, modulos de Apache o utilizando el mod0 IntraWeb Standalone (una variante de la arquitectura del mod0 Application). Los programas en mod0 Page pueden desplegarse como cualquier otra aplicacion WebBroker (ISAPI, modulo de Apache, CGI, etc.. .). IntraWeb usa tres arquitecturas distintas que se solapan en parte:

Modo Standalone: Proporciona un servidor Web personalizado, como en el primer ejemplo creado. Resulta practico para depurar la aplicacion (ya que puede ejecutarse desde el IDE de Delphi y situar puntos de ruptura en cualquier parte del codigo). Tambien se puede usar este mod0 para desplegar aplicaciones en redes internas (intranets) y para permitir que 10s usuarios trabajen en mod0 desconectado en sus propios ordenadores, con una interfaz Web. Si se ejecuta un programa IntraWeb independiente con el indicador -inst a l l , se ejecutara como servicio y no aparecera el cuadro de dialogo. El mod0 Standalone ofrece un mod0 de desplegar un programs IntraWeb de mod0 Application mediante la propia IntraWeb como servidor Web. Modo Application: Permite desplegar una aplicacion IntraWeb en un servidor comercial, construido como modulo Apache o biblioteca ISS. El mod0 Application incluye gestion de sesiones y todas las caracteristicas de IntraWeb, y es el mod0 preferido de desplegar una aplicacion escalable para su uso en la Web. Para ser mas precisos, 10s programas IntraWeb en mod0 Application pueden desplegarse como programas independientes, bibliotecas ISAPI o modulos de Apache. Modo Page: Abre una via a la integracion de paginas IntraWeb en aplicaciones WebBroker y WebSnap. Se pueden aiiadir caracteristicas a programas ya existentes o confiar en otras tecnologias para partes de un sitio dinamico basado en paginas Web, mientras que se gestionan mediante IntraWeb las partes interactivas. El mod0 Page es la unica opcion para utilizar IntraWeb en aplicaciones CGI, per0 carece de las caracteristicas de gestion de sesiones. Los servidores IntraWeb independientes no soporta el mod0 Page.


En 10s ejemplos que apareceran en el resto del capitulo utilizaremos por simplicidad y un proceso de depuracion mas sencillo el mod0 Standalone, per0 tambien hablaremos del soporte del mod0 Page.

Creacion del aplicaciones IntraWeb Cuando se crea una aplicacion IntraWeb, se dispone de un gran numero de componentes. Por ejemplo, si nos fijamos en la pagina IW Standard de la Component Palette de Delphi, se vera una lista impresionante de componentes importantes, desde el boton, casilla de verificacion, boton de radio, cuadro de edicion, cuadro de lista, campo de memo, y demas hasta 10s interesantes componentes de vista en arbol, menu, temporizador, cuadricula y vinculo. No vamos a mostrar una lista de todos 10s componentes y describir su uso con un ejemplo (en lugar de eso usaremos algunos de ellos en unos cuantos programas de demostracion y remarcaremos la arquitectura de IntraWeb en lugar de 10s detalles especificos). Hemos creado un ejemplo (llamado IWTree) en el que se utilizan 10s componentes de menu y de vista en arbol de IntraWeb per0 tambien se crea un componente en tiempo de ejecucion. Este componente tan practico permite acceder en un menu dinamico a1 contenido de un menu estandar de Delphi, haciendo referencia a su propiedad At tachedMenu y a un componente TMenu. o b j e c t MainMenul : TMainMenu o b j e c t Treel: TMenuItem o b j e c t ExpandAlll: TMenuItem o b j e c t CollapseAlll: TMenuItem o b j e c t N1: TMenuItem o b j e c t EnlargeFontl: TMenuItem o b j e c t ReduceFontl: TMenuItem end o b j e c t Aboutl: TMenuItem o b j e c t Applicationl: TMenuItem o b j e c t TreeContentsl: TMenuItem end end o b j e c t IWMenul : TIWMenu AttachedMenu = MainMenul Orientation = iwOHorizonta1 end

Si 10s elementos del menu controlan el evento Onclick en el codigo, se convertiran en enlaces en tiempo de ejecucion. Se puede ver un ejemplo de un menu en la figura 2 1.3. El segundo componente del ejemplo es una vista en arbol con un conjunto de nodos predefinidos. Este componente utiliza mucho codigo JavaScript para permitir la expansion y colapso de 10s nodos directamente en el navegador (sin tener que volver a acceder a1 servidor). A1 mismo tiempo, 10s


elementos del menu permiten que el programa trabaje con el menu expandiendo o colapsando 10s nodos y modificando la fuente. Este es el codigo para un par de controladores de eventos: procedure TformTree.ExpandAlllClick(Sender: TObject); var i: Integer; begin for i : = 0 to 1WTreeViewl.Items.Count - 1 do 1WTreeViewl.Item.s [i].Expanded : = True; end; procedure TformTree.EnlargeFontlClick(Sender: TObject); begin 1WTreeViewl.Font.Size : = 1WTreeViewl.Font.Size + 2; end;

Gracias a1 parecido de 10s componentes de IntraWeb con 10s componentes estandar de la VCL de Delphi, es facil leer y comprender este codigo.

Figura 21.3. El ejemplo IWTree utiliza un menti, una vista en arbol y la creacion dinamica de un componente de memo.

El menu tiene dos submenus, que son bastante mas complejos. El primer0 muestra el identificador de la aplicacion, que es un identificador de la ejecucion y sesion de la aplicacion. Este identificador esta disponible mediante la propiedad


AppI D del objeto global WebApp 1i c a t i o n . El segundo submenu, Tree

Contents, muestra una lista de 10s tres primeros nodos del nivel principal junto con el numero de subnodos directos. Aim asi, lo que es interesante es que la informacion se muestra en un componente de memo creado en tiempo de ejecucion (como muestra la anterior figura 2 1.3.), esactamente del mismo mod0 que en una aplicacion VCL: procedure TformTree.TreeContentslClick(Sender: TObject); var i: Integer; begin w i t h T I W M e m o - C r e a t e (Self) d o begin Parent : = Self; A l i g n : = alBottom; f o r i : = 0 t o 1WTreeViewl.Items.Count - 1 d o Lines .Add ( IWTreeViewl. Items [i].Caption + ' ( ' + IntToStr ( IWTreeViewl. Items [i] .SubItems .Count) ')

+

'1;

end; end; --

TRUCO:Fijese en que el alineamiento en ~ n t r a ~ funciona eb de un m o d ~ parecido a su homologo VCL. Por ejemplo, el menu de este programa tiene un alineamiento a l T o p , la vista en h b o l se alinea como a l C 1 i e n t y el campo d i n h i c o de memo se crea con el alineamiento a l B o t tom. Como alternativa, se pueden usar anclajes (que b c i o n a n como en la VCL): Se pueden crear botones colocados en la esquina inferior derecha o componentes en el centro de la pagina, fijando 10s cuatro anclajes. En 10s siguientes programas de muestras se pueden ver ejemplos de esta tecnica.

Escritura de aplicaciones de varias paginas Todos 10s programas que se han creado hasta ahora tenian una sola pagina. Es hora ya de crear una aplicacion IntraWeb con una segunda pagina. Como se vera, incluso en este caso, el desarrollo mediante IntraWeb se parece a1 desarrollo estandar en Delphi o Kylis, y es diferente de la mayoria del resto de las bibliotecas de desarrollo para Internet. Este ejemplo t a m b i h servira como excusa para profundizar en parte del codigo fuente generado automaticamente mediante el asistente de aplicacion IntraWeb. Comencemos desde el principio. El formulario principal del ejemplo IWTwoForms utiliza una cuadricula de IntraWeb. Este potente componente permite situar en una cuadricula HTML tanto texto como otros componentes. En el ejemplo, el contenido de la cuadricula queda determinado durante el arranque (en el controlador del evento o n c r e a t e del formulario principal):


procedure TformMain.IWAppFormCreate(Sender: TObject); var i: Integer; link: TIWURL; begin // fija 10s titulos de la cuadricula IWGridl.Cell [0, 0] .Text := 'Row'; IWGridl .Cell [0, 11 .Text : = 'Owner'; IWGridl .Cell [0, 2 ] .Text := 'Web Site'; / / fija el contenido de las celdas for i : = 1 to 1WGridl.RowCount - 1 do begin IWGridl .Cell [i,01 .Text : = 'Row ' + IntToStr (i+l); 1WGridl.Cell [i,l] .Text : = 'IWTwoForms by Marc0 Cantu'; link := TIWURL.Create (Self); link.Text := 'Click here '; link .URL : = 'http://www.marcocantu. corn'; IWGridl Cell [i,2 ] .Control : = link; end ; end ;

.

El efecto de este codigo se muestra en la figura 2 1.4. Ademas de la salida, hay que fijarse en unos cuantos detalles interesantes. En primer lugar, el componente de cuadricula utiliza 10s anclajes de Delphi (a False) para generar el codigo que lo mantiene centrado en la pagina, incluso aunque un usuario ajuste el tamaiio de la ventana del navegador. En segundo lugar, hemos aiiadido un componente IWURL a la tercera columna, per0 podria aiiadirse cualquier otro componente (incluidos botones y cuadros de edicion) a la cuadricula. La tercera (y mas importante) cuestion es que un IWGrid se traduce en una cuadricula HTML, con o sin marco alrededor. Este es un fragment0 del codigo HTML generado para una de las filas de la cuadricula: <tr> <td valign="middle" align="leftW NOWRAP> <font style="font-size:lOpt;">Row 2</font> < / td> <td valign="middle" align="leftW NOWRAP> <font style="font-size: 10pt;">IWTwoForms by Marco Cantu</ font> </t& <td valign="middle" align="leftW NOWRAP> <font style="font-size: l0pt; "></font> <a href="#" onclick="parent .LoadURL ( 'http:/ / www.marcocantu.com')" id="TIWURLlr' name="TIWURLl" style="z-index:lOO;font-sty1e:normal;fontsize:lOpt;text-decoration:none;">

Click here</a>

< / td> < /tr>


kowl ~ W ~ ' W O by FM O a~ m Canhl

blick here

1

bow

\ Flick here

I

3

bWwoFoms by Mawo Canhl

Figura 21.4. El ejemplo IWTwoForms usa un componente IWGrid, texto incrustado y componentes IWURL.

TRUCO:En el listado anterior, hay que fijarse en que el UIU. vinculado se activa mediante JavaScript, y no directamente. Se hace asi porque todas las acciones de IntraWeb permiten operaciones adicionales de cliente, como validaciones, comprobaciones y envios. Por ejemplo, si se establece la propiedad Required de un componente, si el campo estiz vacio no se enviaI--o--r-r - - - - - - - a:--1-1ran 10s aaws, -. y se Vera un mensaje ae error ae Javaacrlpr wersonallzaole mediante la propiedad descriptiva F r iendlyName). -1..

> - A _ -

1

1-

3-

3-

T

/

La caracteristica principal del programa es su capacidad de mostrar una segunda pagina. Para realizar esto, en primer lugar se necesita aiiadir una nueva pagina IntraWeb a la aplicacion, mediante la opcion ApplicationForm de la pagina IntraWeb del cuadro de dialogo New Items de Delphi (File>New>Other). Aiiadimos a esta pagina algunos componentes IntraWeb, como siempre, y despues la aiiadiremos un boton u otro control a1 formulario principal que podamos usar para mostrar el formulario secundario (con la referencia a n o t h e r f orm almacenada en un campo del formulario principal): procedure TformMain.btnShowGraphicClick(Sender: TObject); begin anotherform : = TAnotherForm.Create(WebApp1ication);


anotherform.Show; end;

Incluso aunque el programa llame a1 metodo Show, puede considerarse como una llamada a ShowModal, ya que IntraWeb considera las paginas visibles como una pila. La ultima pagina que se muestra esta en la parte superior de la pila y es la que muestra el navegador. A1 cerrar esta pagina (escondiendola o destruyendola), se vuelve a mostrar la pagina anterior. En el programa, las paginas secundarias se pueden cerrar cuando se llama a1 metodo R e l e a s e , que es (corno en la VCL) el mod0 correct0 de deshacerse de un formulario que se ejecuta en ese instante. Tambien se puede esconder el formulario secundario y volverlo a mostrar mas tarde, evitando volver a crearlo cada vez (en particular si hacer esto implica perder las operaciones de edicion del usuario). - -

-. - --

-

-

I

ADVERTENCIA: Hemos &dido en el pronrama un b o t h CIese en el - formulario principal. No deberia llamar a Release, she invocar - en SU lugar a1 mCtodo Terminate del objeto WebApplication, pashdlola el mensaje de salida, como en WebApplication .Terminat e , ... . . . . .-.. .. .. [ ' tiooaoye ! ' ) . La aemostracion urillza una llamaaa alternauva: TerminateAndRedirect. 1

.

.

I

.I

4

'

Ahora que se ha visto como crear una aplicacion IntraWeb con dos formularios, vamos a analizar brevemente el mod0 que se IntraWeb crea el formulario principal. El codigo relevante, generado por el asistente de IntraWeb cuando se crea un programa nuevo, esta en el archivo de proyecto: begin

IWRun(TFormMain,

TIWServerController);

Es algo distinto del archivo de proyecto estandar de Delphi, porque llama a una funcion global en lugar de aplicar un metodo a un objeto global que represente a la aplicacion. No obstante, el efecto es bastante parecido. Los dos parametros son las clases del formulario principal y del controlador IntraWeb, que maneja sesiones y otras caracteristicas, como veremos en breve. El formulario secundario del ejemplo IWTwoForms muestra otra caracteristica interesante de IntraWeb: su extenso soporte de graficos. El formulario tiene un componente grafico con la clasica imagen de Atenea de Delphi. Se consigue esto a1 cargar un mapa de bits en un componente IWImage: IntraWeb convierte el mapa de bits en un archivo JPEG, lo guarda en una carpeta cache creada dentro de la carpeta de la aplicacion y devuelve una referencia a ese archivo, con el siguiente codigo HTML:


La caracteristica adicional proporcionada por IntraWeb y aprovechada por el programa es que un usuario puede hacer clic sobre la imagen con el raton para modificar la imagen a1 ordenar la ejecucion de codigo de servidor. En este programa, el efecto es dibujar pequeiios circulos verdes.

Este efecto se consigue con el codigo siguiente: procedure Tanotherform.IWImagelMouseDown(ASender: const AX, AY: Integer); var aCanvas: TCanvas; begin aCanvas : = 1WImagel.Picture.Bitmap.Canvas; aCanvas.Pen.Width := 8; aCanvas.Pen.Color := clGreen; aCanvas. Ellipse (Ax - 10, A y - 10, Ax + 10, A y end;

TObject;

+

10) ;

rnapa de bits. No hay que intentar utilizar el lienzo &age (como se hacia con el companente TImage de la VCL) y no hay que tratar de war un JPEG en primer Ingar, o no se verh nin& efecto o aparecera un error en tiempo de e j m c i h .

,

Gestion de sesiones Si se ha realizado algo de programacion Web, ya se sabe que la gestion de sesiones es un asunto bastante complejo. IntraWeb proporciona un sistema de gestion de sesiones predefinido y simplifica el mod0 en que se trabaja con sesio-


nes. Si se necesita una sesion de datos para un formulario especifico, todo lo que hay que hacer es afiadir un campo a ese formulario. Los formularies IntraWeb y sus componentes tienen una instancia para cada sesion de usuario. Por ejemplo, en IWSession hemos aiiadido al formulario un campo llamado Formcount.Por contra, tambitn hemos declarado una variable de unidad global llamada Globalcount, que comparten todas las instancias (o sesiones) de la aplicacion. Para aumentar el control sobre 10s datos de sesion y permitir que varios formularios la compartan, se puede personalizar la clase TUserSession que coloca el IntraWeb Wizard en la unidad ServerController. En el ejemplo WSession, hemos particularizado la clase de esta forma: type TUserSession = class public Usercount : Integer; end ;

IntraWeb crea una instancia de este objeto para cada nueva sesion, como puede verse en el metodo IWServerControllerBaseNewSession de la clase TIWServerController en la unidad ServerController predefinida. procedure TIWServerController.IWServerControllerBaseNewSession( ASession: TIWApplication; v a r VMainForm: TIWAppForm); begin ASession.Data := TUserSession-Create; end;

En el codigo de una aplicacion, se puede hacer referencia al objeto de sesion accediendo al campo Data de la variable global RWebApplication,utilizada para acceder a la sesion de usuario actual.

la unidad IWInit. Ofrece acceso a 10s datos de sesibn dc un mod0 seguro con respecto a 10s hilos: hay que tener un cuidado especial para acceder a ella incluso en un entorno rnultihilo. Esta variable puede utilizarse fhera de un formulario o control (que se basan de manera nativa en sesiones), que es por lo que se usa sobre todo en modulos de datos, rutinas globales y clases que no Sean de LntraWeb. Una vez mas, la unidad ServerController predeterminada ofrece una funcion auxiliar que puede utilizarse: function UserSession: TUserSession; begin Result := TUserSession(RWebApp1ication.Data); end;


Ya que la mayor parte del codigo se genera automaticamente, despues de aiiadir datos a la clase TUser ses s ion simplemente hay que usarla mediante la funcion User ses s ion, como en el codigo siguiente, extraido del ejemplo IWSession. Cuando se hace clic sobre un boton, el programa incrementa varios contadores (uno global y dos especificos de sesion) y muestra sus valores en etiquetas: procedure TformMain.IWButtonlClick(Sender: TObject); begin InterlockedIncrement (GlobalCount); Inc (FormCount);

Inc

(UserSession.UserCount);

IWLabell-Text : = 'Global: ' + IntToStr (GlobalCount); IWLabel2 .Text : = 'Form: ' + IntToStr (FormCount); IWLabel3 .Text : = 'User: ' + IntToStr (UserSession.UserCount); end;

Fijese en que el programa utiliza la llamada Inter loc kedI ncrement de Windows para evitar el acceso concurrente a la variable global compartida por varios hilos. Entre las tecnicas alternativas se incluye el uso una seccion critica o de T i d T h r e a d S a f e I n t e g e r de Indy (que se encuentra en la unidad IdTrheadsafe). La figura 2 1.5 muestra el resultado del programa (con dos sesiones en ejecucion en dos navegadores distintos). El programa tambien tiene una casilla de verificacion que activa un temporizador. Aunque suene extraiio, en una aplicacion IntraWeb, 10s temporizadores funcionan casi del mismo mod0 que en Windows. Cuando expira el plazo del temporizador, se ejecuta un cierto codigo. En la Web esto significa refrescar la pagina lanzando una orden de refresco en el codigo JavaScript : ' ,5000); IWTIMERl=setTimeout ( ' S ~ b r n i t C l i c k ( ~ ~ ~ W T I M"", E R l ~false) ~,

Integracion con WebBroker (y WebSnap) Hasta ahora, hemos construido aplicaciones IntraWeb independientes. Cuando se crea una aplicacion IntraWeb en una biblioteca para desplegarla en ISS o Apache, nos encontramos basicamente en la misma situacion. Sin embargo, si se quiere usar el mod0 Page de IntraWeb, las cosas cambian de un mod0 importante. Se trata de integrar una pagina de IntraWeb en una aplicacion Delphi WebBroker (o WebSnap). El puente entre 10s dos mundos es el componente IWPageProducer.Este componente se conecta a una accion de WebBroker como cualquier otro componente productor de paginas y tiene un evento especial que puede usarse para crear y devolver un formulario IntraWeb:


procedure TWebModulel.IWPageProducerlGetForm(ASender: TIWPageProducer; AWebApplication: TIWApplication; var VForm: TIWPageForm); begin VForm : = TformMain.Create(AWebApplication); end;

Con esta sencilla linea de codigo (ademas de un componente IWModuleCon-troller en el modulo Web), la aplicacion WebBroker puede incrustarse en una pagina IntraWeb, como hace el programa CgiIntra. El componente IWModuleController proporciona servicios centrales para el soporte de IntraWeb. Debe existir un componente de este tip0 para que cada proyecto de IntraWeb funcione correctamente.

Global 24

Form 14 User. 14

Figura 21.5. La aplicacion IWSession tiene contadores globales y especlficos de sesion, como puede verse ejecutando dos sesiones en dos navegadores distintos (o incluso en el mismo navegador).


- -

.

-

ADVERTENCIA: La versibn que se incluye con Delphi 7 tiene un problema con el Web App Debugger de Delphi y el componente IWModuleController. Ya se ha solucionado este problema y existe una actualizacion

,

gratuita. Este es un resumen del archivo DFM del mbdulo Web del programa de ejemplo: object WebModulel: TWebModulel Actions = < item Default = True Name = ' W e b A c t i o n I t e m l r PathInfo = ' / s h o w ' OnAction = WebModulelWebActionItemlAction end item Name = ' W e b A c t i o n I t e m . 2 ' PathInfo = ' / i w d e m o r Producer = IWPageProducerl end> object IWModuleControllerl: TIWModuleController object IWPageProducerl: TIWPageProducer OnGetForm = IWPageProducerlGetForm end end

Ya que esta es una aplicacion CGI en mod0 Page, no hay ninguna gestion de sesiones. Aun mas, el estado de 10s componentes de una pagina no se actualiza automaticamente escribiendo controladores de eventos, como en un programa IntraWeb estandar. Para conseguir el mismo efecto se necesita escribir codigo especifico para manejar mas parametros de la peticion HTTP. Deberia quedar claro incluso mediante este ejemplo tan sencillo que el mod0 Page hace menos cosas de mod0 automatic0 que el mod0 Application, pero que es mas flexible. En particular, el mod0 Page de IntraWeb permite aiiadir prestaciones de diseiio RAD visual a las aplicaciones WebBroker y WebSnap.

Control de la estructura El programa CgiIntra utiliza otra interesante tecnologia disponible en IntraWeb: la definition de una estructura personalizada basada en HTML. (Este tema no tiene realmente relacion, ya que las estructuras HTML tambien funcionan en mod0 Application, pero, simplemente, se han usado estas dos tecnicas en un unico ejemplo.) En 10s programas creados h a s h este momento, la pagina resultante es la proyeccion de una serie de componentes colocados en tiempo de diseiio en un formulario, en el que se pueden usar propiedades para modificar el codigo HTML resultante. i Q u l es lo que sucederia si se deseara incrustar un formulario de


entrada de datos en una pagina HTML compleja? Es extrafio construir todo el contenido de una pagina mediante componentes IntraWeb, incluso aunque se pueda usar el componente IWText para incrustar una porcion de HTML personalizado en una pagina IntraWeb. La tecnica alternativa implica el uso de gestores de estructura de IntraWeb. En IntraWeb se usa de manera invariable un gestor de estructura; el predeterminado es el componente IWLayoutMgrForm. Las otras dos alternativas son componentes IWTemplateProcessorHTML para trabajar con un archivo de plantilla HTML externo e IWLayoutMgrHTML para trabajar con HTML interno. Este segundo componente incluye un potente editor HTML que puede usarse para preparar el HTML generic0 al igual que incrustar 10s componentes IntraWeb necesarios (algo que en ocasiones habra que hacer manualmente con un editor HTML esterno). Aun mas, cuando se selecciona un componente IntraWeb desde este editor (que se activa haciendo doble clic sobre un componente IWLayoutMgrHTML), se podra utilizar el Object Inspector de Delphi para personalizar las propiedades del componente. Como puede verse en la figura 2 1.6, el HTML Layout Editor disponible en IntraWeb es un potente editor HTML visual; el texto HTML que genera esta disponible en una pagina aparte. (El editor HTML se mejorara en una proxima actualization, y se arreglaran unos cuantos detalles.)

Html Example code Font test

More text goes here, and you can type it directly. j~nd finally we have some text and a combo box within a grid..

1-

Figura 21.6. El HTML Layout Editor de IntraWeb es un cornpleto editor HTML visual.

En el HTML generado, el HTML define la estructura de Ia pagina. Los componentes solo se marcan con una etiqueta especial basada en Ilaves, como en el ejemplo siguiente:


TRUCQ:Fijese mqw cuando se u w w , los c&p.ona@s nn otilizan el posicioI3ami~& m ~ u t os b que ae ;listrihuyende acukdo con el IfIhlL. Por eso, el form'dari~sc convierte hnipmente en up contenedor de componentes, porque se ignofa la posicib d mulario.

o de 10s companentes del for-

No hace falta decir que el HTML que se ve en el diseiiador visual del HTML Layout Editor se corresponde de manera casi perfecta con el HTML que se puede ver a1 ejecutar el programa en un navegador.

Aplicaciones Web de bases de datos Como en las bibliotecas de Delphi, una parte importante de 10s controles disponibles en IntraWeb tienen que ver con el desarrollo de aplicaciones de bases de datos. El Application Wizard de IntraWeb tiene una version que permite crear una aplicacion con un modulo de datos (un buen punto de partida para el desarro110 de una aplicacion de bases de datos). En este caso, el codigo predefinido de la aplicacion crea una instancia del modulo de datos para cada sesion, guardandolo en 10s datos de sesion. Esta es la clase TUserSession predefinida (y su constructor) para una aplicacion IntraWeb con un modulo de datos: type TUserSession = class(TComponent) public DataModulel: TDataModulel; constructor Create (AOwner: TComponent) ; override; end; constructor TUserSession.Create(AOwner: TComponent); begin inherited; Datamodulel : = TDatamodulel.Create(AOwner); end:

La unidad del modulo de datos no tiene asignada una variable global; si fuera asi, todos 10s datos se compartirian entre todas las sesiones, con una gran posibilidad de problemas en caso de peticiones concurrentes desde varios hilos. Sin embargo, el modulo de datos ya expone una funcion global que tiene el mismo nombre que la variable global que utilizaria Delphi, y que accede a1 modulo de datos de la sesion actual: function DataModulel: TDataModulel; begin Result : = TUserSession(RWebApp1ication.Data) .Datamodulel; end;


Esto significa que se puede escribir un codigo como el siguiente:

Pero en lugar de acceder a un modulo de datos global, se utiliza el modulo de datos de la sesion actual. En el primer programa de ejemplo en el que se incluyen datos de una base de datos, llamado IWScrollData, hemos afiadido a1 modulo de datos un componente S i m p l e D a t a S e t y a1 formulario principal un componente IWDBGrid con la siguiente configuracion: o b j e c t IWDBGridl: TIWDBGrid Anchors = [akLeft, akTop, akRight, akBottom] Bordersize = 1 Cellpadding = 0 CellSpacing = 0 Lines = t l R o w s UseFrame = False DataSource = DataSourcel FromStart = False Options = [dgShowTitles] RowAlternateColor = clSilver RowLimit = 10 RowCurrentColor = clTeal end

La configuracion mas importante es la eliminacion de un marco que albergue el control con sus propias barras de desplazamiento (la propiedad ~ s e ~ r a m e ) , el hecho de que 10s datos se muestren a partir de la posicion del conjunto de datos actual (la propiedad F r o m S t a r t ) y el numero de filas que se mostraran en el navegador (la propiedad RowLimi t ) . En la interfaz de usuario, hemos eliminado las lineas verticales y dado color a filas salteadas. Tambien hemos especificado un color para la fila actual (la propiedad R o w C u r r e n t C o l o r ) ; de no ser asi, 10s colores salteados no aparecerian correctamente, ya que la fila actual tiene el mismo color que las filas del fondo, sin importar su posicion (si se fija la propiedad R o w C u r r e n t C o l o r como c l N o n e se podra ver lo que queremos decir). Estos parametros producen el efecto que muestra la figura 2 1.7. Tambien puede verse si se ejecuta el ejemplo IWScrollData. El programa abre el conjunto de datos cuando se crea el formulario, utilizando el conjunto de datos enlazado con la fuente de datos actual. procedure TformMain.IWAppFormCreate(Sender: begin DataSourcel.DataSet.0pen; end;

TObject);

El codigo relevante del ejemplo esta en el codigo del boton, que puede usarse para recorrer 10s datos mostrando la pagina siguiente o volviendo a la anterior.


Este cs el codigo para uno de 10s dos metodos (el otro no se presenta porque es muy parecido): procedure TformMain.btnNextClick(Sender: TObject); var i: Integer; begin n P o s : = n P o s + 10; if n P o s > DataSourcel.DataSet.RecordCount - 10 then n P o s : = DataSourcel.DataSet.RecordCount - 10; DataSourcel.DataSet.First; for i : = 0 to nPos do DataSource1.DataSet.Next; end;

Figura 21.7. La cuadricula data-aware del ejemplo IWScrollData.

Enlaces con detalles La cuadricula del ejemplo IWScrollData muestra una unica pagina de una tabla de datos; 10s botones permiten desplazarse hacia arriba y abajo por las paginas. Un estilo de cuadricula alternativo en IntraWeb es el que ofrecen las cuadriculas con marcos, que pueden mover cantidades aun mas grandes de datos hacia el navegador Web dentro de un area de pantalla de un tamaiio fijo utilizando un marco y una barra de desplazamiento interna, como haria un control ScrollBox de Delphi. Esto se demuestra en el ejemplo IWGridDemo. El ejemplo personaliza la cuadricula de un segundo mod0 muy potente: establece la propiedad de conjunto c o l u m n s de la cuadricula. Este parametro permite ajustar con precision el aspecto y el comportamiento de columnas especificas, mostrando por ejemplo hipervinculo o controlando 10s clics sobre celdas de elementos o titulos. En el ejemplo IWGridDemo, una de las columnas (la del apelli-


do) se ha convertido en un hipervinculo; se pasa el numero de empleado como parametro a1 comando de continuar, como muestra la figura 2 1.8.

Figura 21.8. El formulario principal del ejemplo IWGridDemo utiliza una cuadricula con marco con hipervinculos hacia el formulario secundario.

El listado 2 1 . 1 , muestra un resumen de las propiedades clave de la cuadricula. Fijese en particular en la columna del apellido, que tiene un campo enlazado (lo que convierte a1 texto de la celda en un hipervinculo) y un controlador de evento que responde a su seleccion. En este metodo; el programa crea un formulario secundario mediante el cual el usuario puede editar 10s datos: p r o c e d u r e TGridForm.IWDBGridlColurnns1C~ick(ASender: TObject; c o n s t AValue: String) ; begin w i t h TRecordForm.Create (WebApplication) d o begin S t a r t I D : = AValue; Show; end; end:

Listado 21.1. Propiedades de IWDBGrid en el ejemplo IWGridDemo. o b j e c t IWDBGridl: TIWDBGrid A n c h o r s = [akLeft, akTop, akRight, akBottom]


UseFrame = True Usewidth = True Columns = < item Alignment = taLeftJustify BGColor = clNone DoSubmitValidation = True Font-Color = clNone Font-Enabled = True Font.Size = 10 Font-Style = [ I Header = False Height = '0' VAlign = vaMiddle Visible = True Width = ' 0 ' Wrap = False BlobCharLimit = 0 CompareHighlight = hcNone DataField = ' F I R S T - N A M E ' Title.Alignment = taCenter Title.BGColor = clNone Title.DoSubmitVa1idation = True Title.Font.Color = clNone Tit1e.Font.Enabled = True Title.Font.Size = 10 Title.Font.Style = [ I Title.Header = False Title-Height = '0' Title.Text = ' F I R S T - N A M E ' Title.VAlign = vaMiddle Title.Visible = True Title.Width = '0' Title.Wrap = False end item

DataField = 'LAST-NAME' LinkField = 'EMP-NO ' OnClick = IWDBGridlColumnslClick end item

DataField = ' H I R E - D A T E ' end item

DataField =

' JOB-CODE'

end item

DataField =

' JOB-COUNTRY'

end item

DataField = end item

' JOB-GRADE'


DataField = 'PHONE-EXT' end> Datasource = DataSourcel Options = [dgShowTitles] end

A1 establecer la propiedad Start I D del segundo formulario, se puede encontrar el registro apropiado: procedure TRecordForm.SetStartID(const Value: string); begin FStartID : = Value; Value, [ I ) ; DataSourcel .Dataset .Locate ( 'EMP-NO', end;

otras operaciones sobre la columna. El formulario secundario esta enlazado con el mismo modulo de datos que el formulario principal. Por eso; despues de actualizar 10s datos de la base de datos, se pueden ver en la cuadricula (pero las actualizaciones se guardan solo en memoria. porque el programa no realiza una llamada a ApplyUpdates). El formulario secundario utiliza unos cuantos controles de edicion y un navegador, proporcionado por IntraWeb. La figura 2 1.9 muestra este formulario en tiempo de ejecucion.

maLast Name Hire

-

u

estan

1111 711 990

Figura 21.9. El formulario secundario del ejemplo IWGridDemo permite que un usuario edite 10s datos y explore 10s registros.


Transporte de datos al cliente Sin tener en cuenta como se utilice, el componente IWDBGrid produce HTML con 10s datos de la base de datos incrustados en las celdas, per0 no puede trabajar con 10s datos en el lado del cliente. Un componente distinto (o un conjunto de componentes de IntraWeb) permite utilizar un modelo distinto. Los datos se envian a1 navegador en un formato personalizado, y el codigo JavaScript del navegador rellena la cuadricula y trabaja con 10s datos, pasando de un registro a otro sin solicitar mas datos a1 servidor. Se pueden usar varios componentes IntraWeb para una aplicacion cliente, per0 estos son algunos de 10s mas importantes:

IWClientSideDataSet: Un conjunto de datos en memoria que se define fijando las propiedades ColumnName y Data en el codigo del programa. En futuras actualizaciones se podra editar datos en el cliente, ordenarlos, filtrarlos, definir estructuras maestro-detalle y muchas cosas mas. IWClientSideDataSetDBLink: Un proveedor de datos que puede conectarse a cualquier conjunto de datos de Delphi, conectandolo con la propiedad Datasource.

IWDynGrid: Un componente de cuadricula dinamica conectado con uno de 10s dos componentes anteriores mediante la propiedad D a t a . Este componente lleva todos 10s datos a1 navegador y puede trabajar con ellos en el cliente mediante JavaScript. Existen otros componentes de cliente en IntraWeb, como IWCSLabel, IWCSNavigator e IWDynamicChart (que solo funciona con Internet Explorer). Como un ejemplo del uso de esta tecnica, hemos construido el ejemplo IWClientGrid. El programa tiene poco codigo, per0 que hay mucho preparado para su uso en 10s componentes. Estos son 10s elementos centrales de su formulario principal: object formMain: TformMain SupportedBrowsers = [brIE, brNetscape61 OnCreate = IWAppFormCreate object IWDynGridl: TIWDynGrid Align = alClient Data = IWClientSideDatasetDBLinkl end object DataSourcel: TDataSource Left = 72 Top = 8 8 end object IWClientSideDatasetDBLinkl: TIWClientSideDatasetDBLink Datasource = DataSourcel end end


El conjunto de datos procedente del modulo de datos se conecta con el Datasource cuando se crea el formulario. La cuadricula resultante, que muestra la figura 2 1.10, permite ordenar 10s datos en cualquier celda (mediante la pequeiia flecha que se encuentra despues del titulo de cada columna) y filtrar 10s datos mostrados seglin uno de 10s valores posibles del campo. Por ejernplo. en la figura se pueden ordenar 10s datos de empleado de acuerdo con el apellido y filtrarlos por pais y categoria laboral.

lC'

LI

I

TJ

Green

Tcm Luke Carol Mary Leshe K J Randy Mchael

Lcc

Leung

Nordskom pas Phong Wcrton Wdhams

Yanowslu

4 4 4 4

4 4 4 4 4

USA USA USA USA USA USA USA USA USA

Figura 21.10. La cuadricula del ejernplo IWClientGrid soporta la ordenacion y filtrado personalizados sin tener que volver a traer 10s datos desde el servidor Web.

Esta caracteristica es posible porque 10s datos se llevan a1 navegador dentro del codigo JavaScript. Este es un fragmente de uno de 10s guiones incrustados en el codigo HTML de la pagina: < s c r i p t language="Javascriptl.Z"> var IWDYNGRIDl-Titlecaptions = [ "EMP-NO", "FIRST-NAME ","LAS T-NAME", "PHONE-EXT", "DEPT-NO " ,"JOB- CODE " ,"JOB-GRADE " ,"JOB- COUNTRY "1 ; var IWDYNGRIDl-Cellvalues = new Array(); IWDYNGRID1-CellValues[O] = [ Z , 'Robert', 'Nelson', '332', '600', 'VP',21 'USA'] ; IWDYNGRID1-CellValues[l] = [ 4 , 'Bruce', 'Young', '233', '621 ', ' E n g ' , 2 1' U S A ' ] ; IWDYNGRIDl-CellValues[Z] = [ 5 , 'Kim', 'Lambert ', '22', ' 1 3 O 1 ,' E n g 1 , 2 ,'USA']; IWDYNGRID1-Cellvalues [3] = [8, 'Leslie', 'Johnson', '410', ' 1 8 0 r ,'Mktg',3, 'USA'] ; IWDYNGRID1-CellValues[4] = [ 9 , 'Phil', 'Forest', '229', ' 6 2 Z 1 ,' M n g r ' , 3 1' U S A ' ] ;

El motivo para utilizar este enfoque basado en JavaScript en lugar de un enfoque basado en XML como el utilizado en otras tecnologias parecidas, es que solo Internet Explorer ofrecer soporte para islas de datos XML. Mozilla y Netscape carecen de esta caracteristica y tienen un soporte muy limitado de XML.


tecnolog~as XML

Crear aplicaciones para Internet significa usar protocolos y crear interfaces de usuario basadas en navegadores, como en 10s dos capitulos anteriores, per0 tambien abre una oportunidad para el intercambio de documentos de negocio electronicamente. Los estandares que surgen para este tip0 de actividad se centran en el formato de documento XML e incluyen el protocolo de transmision SOAP, 10s esquemas XML para la validacion de documentos y XSL para representar documentos como HTML. En este capitulo, comentaremos las principales tecnologias XML y el amplio soporte que Delphi les ofrece desde su version 6. Ya que el conocimiento sobre XML no esta muy extendido, vamos a ofrecer una pequeiia presentacion sobre cada tecnologia, per0 deberian consultarse libros dedicados especialmente a estas tecnologias para aprender mas. En el capitulo 23 nos centraremos de manera especifica en 10s servicios Web y SOAP. En este capitulo se tratan 10s siguientes temas: Presentacion de XML: Extensible Markup Language Trabajo con un DOM XML. Delphi y XML: interfaces y proyeccion. Procesamiento de XML con SAX.


Internet Express. Uso de XSLT. XSL en WebSnap.

Presentacion de XML El lenguaje extensible de marcas (extens~bleMarkup Language, XML) es una version simplificada de SGML y recibe mucha atencion en el mundo de las tecnologias de la informacion. XML es un lenguaje de marcas, que quiere decir que utiliza simbolos para describir su propio contenido (en este caso, etiquetas que consistente en un texto definido de manera especial, encerrado entre 10s caracteres < y >). Es extensible porque permite usar marcas libres (en contraste con, por ejemplo, HTML, que tiene marcas predefinidas). El lenguaje XML es un estandar promocionado por el World Wide Web Consortium (W3C). La recomendacion XML puede encontrarse en www.w3.org/TR/REC-xml. Se ha llamado a XML el ASCI del aiio 2000, para indicar que es una tecnologia simple y muy extendida y tambien que un documento XML es un archivo de texto plano (de manera opcional con caracteres Unicode en lugar de simple texto ASCII). La caracteristica mas importante de XML es que es descriptivo, ya que cada etiqueta tiene un nombre casi legible para un humano. Este es un ejemplo, en caso de que jamas se haya visto un documento XML: <book> <title>La biblia de Delphi 7</title> <author>Cantu</author> <publisher>Anaya</publisher> </book>

XML presenta unas cuantas desventajas que estaria bien resaltar desde el principio. La mas importante es que sin una descripcion formal, un documento vale de poco. Si se quiere intercambiar documentos con otra empresa, hay que llegar a un acuerdo sobre lo que significa cada etiqueta y tambien sobre el significado semantic0 del contenido. (Por ejemplo, cuando se tiene una cantidad, hay que acordar el sistema de medida o incluirlo en el documento.) Otra desventaja es que 10s documentos XML son mucho mayores que otros formatos. Por ejemplo, usar cadenas para 10s numeros no es nada eficiente, y las etiquetas de apertura y cierre ocupan mucho espacio. Lo bueno es que XML se comprime muy bien, por el mismo motivo.

Sintaxis XML basica Merece la pena conocer algunos elementos tecnicos de XML antes de analizar su uso en Delphi. Este es un resumen de 10s elementos clave de la sintaxis XML:


Los espacios en blanco (corno el caracter de espacio, el retorno de carro, el salto de linea y 10s tabuladores) generalmente se ignoran (corno en un documento HTML). Es importante dar formato a un documento XML para que resulte legible, per0 a 10s programas no les importara demasiado. Se pueden aiiadir comentarios dentro de las marcas < ! -- y -->, que, en esencia, ignoran 10s procesadores de XML. Existente tambien directivas e instrucciones de proceso, encerradas entre las marcas < ? y ? > . Existen unos pocos caracteres especiales o reservados que no pueden usarse en el texto. Los dos unicos simbolos que no pueden usarse jamas son el caracter menor que (<, usado para delimitar una marca), que se sustituye por & 1t ; y el caracter ampersand (&), que se sustituye por &amp ; (y es la evolucion grafica del et latino). Otros caracteres especiales optativos son & g t ; para el simbolo mayor que (>), & apo s ; para la comilla simple ( ' ) y &quot para la comilla doble ("). Para aiiadir contenido que no sea XML (por ejemplo, informacion binaria o un guion), se puede usar una seccion CDATA, delimitada por < ! [CDATA[ y ] I > . Todas las etiquetas se encuentran entre 10s simbolos menor y mayor que, < y >. Las marcas son sensibles a las mayusculas (no como en HTML). Por cada marca de apertura, debe existir una marca de cierre correspondiente, indicada por un caracter inicial de barra inclinada:

Las marcas no pueden solaparse: deben anidarse correctamente, como en la primera linea que se muestra (la segunda linea no es correcta): <node>xx <nested> yy</nested> </node> <node>xx <nested> yy</node> </nested>

/ / correct0 / / erroneo

Si una marca no tiene contenido (pero su presencia resulta importante), pueden sustituirse las marcas de apertura y cierre por una marca unica que incluye una barra inclinada final: <node / >. Las marcas pueden tener atributos, usando varios nombres de atributos seguidos de un valor encerrado entre comillas:

Cualquier nodo XML puede tener varios atributos, varias etiquetas incrustadas y un unico bloque de texto que representa el valor del nodo. Es habitual que 10s nodos XML tengan un valor textual o etiquetas incrustadas, y no ambas variantes. Este es un ejemplo de la sintaxis completa de un nodo:


Un nodo puede tener varios nodos hijo con la misma etiqueta (las etiquetas no tiene por que ser unicas). Los nombres de atributos son unicos para cada nodo.

XML bien formado Los elementos comentados en la seccion anterior definen la sintaxis de un documento XML, per0 no bastan. Un documento XML se considera correcto sintacticamente, o bien formato, si sigue unas cuantas reglas adicionales. Fijese en que este tipo de comprobacion no garantiza que el contenido del documento sea significative, solo que las etiquetas esten bien dispuestas. Cada documento deberia tener un prologo que indica que es de hecho un documento XML, q u i version de XML cumple y posiblemente el tip0 de codificacion de 10s caracteres. Este es un ejemplo:

Entre las codificaciones posibles hay conjuntos de caracteres Unicode (como UTF-8, UTF- 16 y UTF-32) y algunas codificaciones I S 0 (como ISO- 10646-xxx o 1SO-8859-xss). El prologo tambien puede incluir declaraciones externas, el esquema usado para validar el documento, declaraciones de espacios de nombre, un archivo XSL asociado y algunas declaraciones de entidades internas. Consulte documentacion o libros sobre XML para conseguir mas informacion sobre estos temas. Un documento XML esta bien formado si tiene un prologo, tiene una sintaxis correcta (segun las reglas de la seccion anterior) y tiene un arb01 de nodos dentro de una raiz unica. La mayoria de las herramientas (como Internet Explorer) comprueban si un documento esta bien formado a1 cargarlo.

NOTA: XML es miis formal y precis0 que HTML.El W3C trabaja en un estandar XHTML que hara que los docurnentos HTML sean confonnes con XML, para que las herramientas XML 10s prowsen mejor. Esta implica muchos carnbios en un docurnento HTML tipico, d o evitar 10s atributos sin valores, a f d i r todas las marcas de cierie (wmo m </p> y </li>), ahdir la barra invertida para marcas independientes (cam0 <hr / > y â&#x201A;Źbr/ >), un anidado correcto y muchas cosas mais. El sitio Web de W3C alberga u n cnnvercnr de HTMT, a XHTMT. Ilnmdn HTMI. Tidv en www w 3 mnl


Trabajo con XML Para acostumbrarse al formato de XML, se puede usar uno de 10s editores XML disponibles en el mercado (incluidos Delphi y Context, un editor para programadores escrito en Delphi). Cuando se carga un documento XML en Internet Explorer, se puede ver si es correct0 y, en ese caso, visualizarlo en el navegador con una estructura en arbol. (En el momento de escribir esto, otros navegadores tienen un soporte XML mas limitado.) Para acelerar este tipo de operacion, hemos creado el editor XML mas simple posible, basicamente un campo de memo con comprobacion de sintaxis XML y un navegador conectado. El ejemplo XmlEditOne tiene un Pagecontrol con tres paginas. La primera pagina, Settings, contiene un par de componentes en 10s que se puede escribir la ruta y el nombre del archivo con el que se quiere trabajar. (El motivo de no utilizar un dialog0 estandar quedara claro cuando mostremos una ampliacion del programa.) El cuadro de edicion que contiene el nombre completo del archivo se actualiza automaticamente con la ruta y el nombre de archivo, si esta seleccionado el cuadro de verificacion Autoupdate. La segunda pagina contiene un control de memo; se carga y se guarda el texto del archivo XML haciendo clic sobre 10s dos botones de la barra de herramientas. En cuanto se carga el archivo, o cada vez que se modifica su texto, su contenido se carga en un DOM para que un analizador sintactico o parser compruebe su correccion (algo que seria complejo hacer con codigo propio). Para procesar el codigo, hemos usado el componente XMLDocument disponible en Delphi, que es basicamente un envoltorio de un DOM disponible en el ordenador e indicado mediante su propiedad ~ b ~ v e n d oComentaremos r. el uso de este componente con mayor detalle en la siguiente seccion. Por el momento, baste con decir que se puede asignar una lista de cadena a esta propiedad XML y activarla para permitir que procese el texto XML e informe tal vez de un error mediante una excepcion. Para este ejemplo, este comportamiento dista de ser bueno, ya que mientras se escribe el codigo XML se tendra codigo XML temporalmente incorrecto. Aun asi, hemos preferido no pedir a1 usuario que haga clic sobre un boton para realizar la validacion, sino ejecutarla de manera continua. Ya que no es posible inhabilitar la excepcion de proceso lanzada por el componente XMLDocument, hemos tenido que trabajar a un nivel mas bajo, extrayendo la propiedad DOMPersist (que hace referencia a la interfaz de permanencia de DOM) despues de extraer la interfaz IXMLDo cumentAccess del componente XMLDocument, llamado XmlDoc en este codigo. Tambien puede extraerse la interfaz I DOMPars eError del componente del documento, para mostrar cualquier mensaje de error en la barra de estado: procedure TFormXmlEdit.MemoXmlChange(Sender: var eParse: IDOMParseError; begin

TObject);


XmlDoc.Active : = True; xmlBar. Panels [l] .Text := 'OK'; xmlBar Panels [2] .Text : = ' '; (XmlDoc as IXMLDocumentAccess) .DOMPersist loadxml (MemoXml .Text) ; eParse := (XmlDoc.DOMDocument as IDOMParseError) ; i f eParse. errorcode <> 0 then with eParse do begin xmlBar. Panels [1] .Text : = 'Error in: ' + IntToStr (Line) + '. ' + IntToStr (LinePos); xmlBar. Panels [2] .Text : = SrcText + ': ' + Reason; end; end;

.

.

La figura 22.1 muestra un ejemplo de la salida del programa, junto con la vista en arb01 XML que ofrece la tercera pagina (para un documento correcto). La tercera pagina del programa se construyo mediante el componente WebBrowser, que incluye un control ActiveX de Internet Explorer. Lamentablemente, no esiste un mod0 direct0 de asignar una cadena con testo XML a este control, por lo quc habra que guardar el archivo en primer lugar para luego pasar a esta pagina para iniciar la carga del XML en el navegador (despues de hacer clic a mano sobre el boton Refresh a1 menos una vez).

Figura 22.1. El ejemplo XmlEditOne permite escribir texto XML en un componente de memo, indicando 10s errores durante la escritura y mostrando el resultado en el navegador incluido. -

. -

--

.-

-

-

NOTA: Hemos utilizado este codigo como punto de partida para crear un editor XML cornpleto llamado XrnlTypist. Incluye resaltado de sintaxis, soporte XSLT y unas cuantas caracteristicas adicionales. En el apCndice A se puede consultar la disponibilidad de este editor XML gratuito.

Manejo de documentos XML en Delphi Ahora que ya se conocen 10s elementos principales de XML, podemos comenzar a analizar como se manejan 10s documentos XML en programas Delphi (o en


programas en general, ya que algunas de las tecnicas que vamos a ver van mas alla del lenguaje que se utilice). Hay dos tecnicas basicas para manipular documentos XML: utilizar una interfaz de modelo de objeto de documento (Document Object Model, DOM) o utilizar una API para XML sencilla (Simple API for XML, SAX). Los dos enfoques son bastante distintos:

DOM: Carga un documento completo en un arbol jerarquico de nodos, lo que nos permite leerlos y manipularlos para modificar el documento. Por ello, el DOM es aconsejable para navegar por la estructura XML en memoria, editarla e incluso para crear documentos completamente nuevos. SAX: Analiza sintacticamente el documento, lanzando un evento para cada elemento del documento sin crear ninguna estructura en memoria. Despues de que SAX haya analizado el documento, este se pierde, per0 este mod0 de funcionamiento suele ser mucho mas rapido que crear el arbol DOM. Usar SAX esta bien si el documento se va a leer de una vez, por ejemplo, si se busca una parte de sus datos. Existe una tercera posibilidad para manipular (y en concreto para crear) documentos XML: el manejo de cadenas. Crear un documento aiiadiendo cadenas es, sin duda alguna, la operacion mas rapida si podemos dar una sola pasada (y no necesitamos modificar 10s nodos ya generados). Incluso la lectura de documentos por medio de funciones de cadenas es muy rapida, per0 puede complicarse para estructuras complejas. Ademas de estos enfoques clasicos del procesamiento de XML, que tambien estan disponibles para otros lenguajes de programacion, Delphi 6 proporciona dos tecnicas mas que deberiamos tener en cuenta. La primera es la definicion de interfaces que proyectan la estructura del documento y que se utilizan para acceder a1 mismo en lugar de hacerlo a traves de la interfaz generica de DOM. Como veremos, este metodo contribuye a una codification mas rapida y aplicaciones mas solidas. La segunda tecnica es el desarrollo de transformaciones que nos permitan leer un documento XML generic0 dentro de un componente ClientDataSet o guardar el conjunto de datos en un archivo XML con una estructura dada (no en la estructura XML especifica que soporta nativamente el ClientDataSet o MyBase). No vamos a tratar de decidir que opcion es la que mejor se adapta a cada tip0 de documento y manipulacion, per0 resaltaremos algunas de las ventajas e inconvenientes mientras analizamos ejemplos de cada enfoque en las secciones siguientes. A1 final del capitulo, analizaremos la velocidad relativa de las tecnicas para el procesamiento de grandes archivos.

Programacion con DOM Ya que un documento XML tiene una estructura parecida a un arbol, cargar un documento XML en un arbol en memoria es una operacion bastante natural. Esto


es lo que hace DOM. DOM es una interfaz estandar, por lo que cuando se ha escrito codigo que utiliza un arb01 DOM, podemos cambiar de implementacion de DOM sin alterar el codigo fuente (a1 menos si no hemos utilizado extensiones personalizadas). En Delphi se pueden instalar varias implementaciones de DOM, disponibles como servidores COM, y utilizar sus interfaces. Uno de 10s motores DOM mas utilizados en Windows es el que proporciona Microsoft como parte del MSXML SDK, per0 que tambien instala Internet Explorer (y por ello todas las versiones recientcs de Windows) y muchas otras aplicaciones de Microsoft. (Con el MSXML SDK cornpleto tambien se incluye documentacion y ejemplos bastante detallados que no se conseguiran en otras instalaciones de la misma biblioteca incluidas con otras aplicaciones.) Otros motores DOM disponibles directamente en Delphi 7 son Xerces, de la fundacion Apache y OpenXML, de codigo abierto. TRUCO: OpenXML es un motor DOM nativo en Object Pascal disponible en www.philo.de/xml. Otro motor DOM nativo en Delphi lo ofrece Turbopower. Estas soluciones tienen dos ventajas. No necesitan una bi.. . . . a ouoteca externa para que se ejecure el programs, ya que el componente DOM se compila con la aplicacion; y son multiplataforma.

.

I

Delphi incluye las implementaciones DOM en un componente envoltorio Ilamado XMLDocument. Hemos usado este componente en el ejemplo anterior, per0 esaminaremos su papel en un aspect0 mas general. La idea de usar este componente en lugar de la interfaz DOM es permanecer independientes de las implementaciones y poder trabajar con metodos simplificados, o auxiliares. El uso de la interfaz DOM es bastante complejo. Un documento es un conjunto de nodos, cada uno con un nombre, un elemento de texto, un conjunto de atributos y un conjunto de nodos hijo. Cada conjunto de nodos permite el acceso a 10s elementos a traves de su posicion o buscandolos por nombre. Observese que el texto que se encuentra dentro de las etiquetas de un nodo, si hay alguno, se representa como un hijo como del nodo y se listara en el conjunto de nodos hijo. El nodo raiz tiene algunos metodos adicionales para crear nuevos nodos, valores o atributos. Con el XMLDocument de Delphi podemos trabajar a dos niveles: A un nivel inferior, podemos utilizar la propiedad DOMDocument (del tip0 de interfaz ~DOMDocument)para acceder a la interfaz estandar W3C Document Object Model. La interfaz DOM oficial se define en la unidad xmldom e incluye interfaces como IDOMNode, IDOMNodeList, IDOMAttr, IDOMElement e IDOMText. Con las interfaces DOM oficiales, Delphi soporta un modelo de programacion estandar per0 de bajo nivel. La implementacion de DOM la indica el componente XMLDocument en la propiedad DOMVendor.


A un nivel superior, el componente XMLDocument implementa tambien la interfaz IXMLDocument. Se trata de una API personalizada del tip0 de DOM definida por Borland en la unidad XMLIntf y que incluye interfaces como IXMLNode, IXMLNodeList e IXMLNodeCollection. Esta interfaz de Borland simplifica algunas de las operaciones de DOM sustituyendo varias llamadas a metodos, que suelen repetirse a mod0 de secuencia, por una sola propiedad o metodo. En 10s siguientes ejemplos (sobre todo en el ejemplo DomCreate), utilizaremos ambos enfoques para dar una mejor idea de las diferencias practicas entre ambos.

Un documento XML en una TreeView Normalmente, el punto de partida consiste en cargar un documento desde un archivo o crearlo a partir de una cadena, per0 tambien podemos empezar con un documento completamente nuevo. Como primer ejemplo de la utilizacion de DOM, hemos creado un programa que carga un documento XML en un DOM y muestra su estructura en un control TreeView. Tambien hemos aiiadido a1 programa XmlDomTree unos botones con codigo de muestra usados para acceder a 10s elementos de un archivo de prueba, como un ejemplo del acceso a 10s datos DOM. Cargar el documento es sencillo, per0 mostrarlo en un arb01 requiere una funcion recursiva que recorra 10s nodos y subnodos. Este es el codigo para 10s dos metodos : p r o c e d u r e TFormXmlTree.btnLoadClick(Sender: TObject); begin 0penDialogl.InitialDir : = ExtractFilePath ( A p p l i c a t i o n .ExeName) ; i f 0penDialogl.Execute t h e n begin XMLDocumentl.LoadFromFile(OpenDialogl.Fi1eName); Treeviewl.1tems.Clear; DomToTree (XMLDocumentl.DocumentElement, nil); TreeViewl-FullExpand; end; end: p r o c e d u r e TFormXmlTree.DomToTree (XmlNode: IXMLNode; TreeNode: TTreeNode) ; var I: Integer; NewTreeNode: TTreeNode; NodeText: string; AttrNode: IXMLNode; begin // omite nodos d e texto y otros casos especiales i f (XmlNode.NodeType <> ntElement) t h e n Exit;


// afiade e l p r o p i o nodo NodeText : = XmlNode.NodeName; i f XmlNode.1sTextElement then NodeText : = NodeText + ' = ' + XmlNode.NodeValue; NewTreeNode : = TreeViewl.Items.AddChild(TreeNode, NodeText); // a t i a d e s u s a t r i b u t o s f o r I := 0 t o xmlNode.AttributeNodes.Count - 1 d o begin AttrNode : = xmlNode.AttributeNodes.Nodes[I]; TreeViewl.Items.AddChild(NewTreeNode, ' I ' + AttrNode.NodeName + ' = " ' + AttrNode.Text + " ' 1 ' ) ; end; // afiade cada nodo h i j o i f XmlNode.HasChildNodes then f o r I : = 0 t o xmlNode.ChildNodes.Count - 1 d o DomToTree (xmlNode.Chi1dNodes.Nodes [I], NewTreeNode); end;

Este codigo es bastante interesante ya que resalta algunas de las operaciones que podemos realizar con un DOM. En primer lugar, cada nodo tiene una propiedad NodeType que podemos usar para determinar si el nodo es un elemento, un atributo, un nodo de testo o una entidad especial (como CDATA y otras). Ademas, no podemos acceder a la representacion textual del nodo, su Nodevalue, a menos que tenga un elemento de testo (el nodo de texto se omitira, como comprobacion inicial). Despues de mostrar el nombre del elemento y el valor del testo, si esta disponible, el programa muestra directamente el contenido de cada atributo y de cada subnodo llamando de manera recursiva a1 metodo DomToTree (vease figura 22.2).

a u h r = Canlu

i 3 book title = Delphi Devdoper'sHandbook aulhol = Canlu aulhor = Gaoch

r3boak litle = MarletingDelph~6 aulho~= Canlu

@i:book E book El ebwk l i b = EssenlidPascd utl = hllp:Nwww.marwcantucorn wthm = Canlu 8 ebmk title = Thinking in Java url = hllp:llwww,mindview.com aulhu = Eckel

Figura 22.2. El ejernplo XmlDomTree puede abrir un documento XML generic0 y mostrarlo dentro de un control TreeView cornun.


Una vez que hayamos cargado el documento de muestra que acompaiia a1 programa XmlDomTree (mostrado en el listado 22.1) en el componente XMLDocument, podemos utilizar diversos metodos para acceder a nodos genericos, como en el anterior codigo de construccion del arbol, o buscar elementos especificos. Por ejemplo podemos obtener el valor del atributo t e x t del nodo raiz si escribimos: XMLDocumentl.DocumentElement.Attributes

['text']

Hay que tener en cuenta que si no hay ningun atributo llamado t e x t , la llamada fallara con un mensaje de error generico: "Invalid variant type conversion" (conversion de tip0 variante invalida). Si necesitamos acceder a1 primer atributo de la raiz y no conocemos su nombre, podemos utilizar el siguiente codigo:

Para acceder a 10s nodos, utilizamos una tecnica similar, aprovechandonos posiblemente de la matriz ChildValues.Se trata de una extension de Delphi a DOM, que nos permite pasar como parametro el nombre del elemento o su posicion numerica:

Este codigo consigue el (primer) autor del segundo libro. No podemos utilizar la expresion Chi 1 dVa lues [ ' book ' ] , ya que hay varios nodos con el mismo nombre bajo el nodo raiz. Listado 22.1. El docurnento XML de muestra utilizado en 10s ejernplos de este capitulo. <?xml version="l.OW encoding="UTF-8"?> <books t e x t = " B o o k s W > <book> <title>La biblia de Delphi 7</title> <author>Cantu</author> </book> <book> <title>Delphi Developer's Handbook</title> <author>Cantu</author> <author>Gooch</author> </book> <book> <title>Delphi COM Programming</title> <author>Harmon</author> </book> <book> <title>Thinking i n C++</title> <author>Eckel</author> </book>


Creacion de documentos utilizando DOM Aunque hemos mencionado anteriormente que se puede crear un documento XML agrupando cadenas, Csta tecnica no es la mas robusta. Usar DOM para crear un documento garantiza que el XML estara bien formado. Ademas, si a1 DOM se el adjunta una definition de esquema, podemos validar la estructura del documento mientras le aiiadimos datos. Para resaltar 10s diferentes casos de creacion de un documento, hemos construido el ejemplo DomCreate. Este programa puede crear documentos XML dentro del DOM, mostrando su texto en un campo de memo y, opcionalmente, en una TreeView

para mejorar la salida al memo del texto XML, fo&tehdolo m$or. Podemos escoger el sangrado estqbleciendo la propie* ..$ Node- tip0- de .--. nco, tamb16 podemos .blecidos dos espacios ,no hay forma alguna d

*

El primer boton del formulario, Simple, crea un texto XML sencillo utilizando las interfaces oficiales de bajo nivel de DOM. El programa llama a1 metodo creat eElement del documento para cada nodo, aiiadiendolos como hijos de otros nodos: procedure TForml.btnSimpleClick(Sender: TObject); var iXml: IDOMDocument; iRoot, iNode, iNode2, iChild, iAttribute: IDOMNode; begin / / v a c i a e l docurnento XMLDoc.Active : = False; XMLDoc. XML-Text := "; XMLDoc.Active : = True;


/ / raiz iXml : = XmlDoc.DOMDocument; iRoot : = iXml.appendChild (iXml.createElement ( ' x m l ' ) ) ; / / nodo "test" iNode : = iRoot.appendChild (iXml.createElement ('test')); iNode.appendChild (iXml.createElement ('test2')); iChild : = iNode.appendChild (iXml.createElement ('test3')); iChild. appendchild (iXml.createTextNode ( 'simple value ' ) ) ; iNode.insertBefore (iXml.createElement ('testd'), iChild); / / replica de nodo iNode2 : = iNode. cloneNode (True); iRoot.appendChild (iNode2); / / adade un atrlbuto iAttribute .nodevalue : = 'red '; iNode2.attributes.setNamedItem

(iAttribute);

/ / muestra XML en el memo Memol.Lines.Text : = FormatXMLData

(XMLDoc.XML.Text);

end;

Fijese en que 10s textos de 10s nodos se aiiaden explicitamente, que 10s atributos se crean con una llamada de creacion especifica y que el codigo utiliza cloneNode para hacer una replica de una rama entera del arbol. Globalmente, la escritura del codigo es un poco engorrosa, per0 se acostumbrara a1 estilo. El efecto del programa se muestra en la figura 22.3 (con formato en el memo y en el arbol).

9x d E test test2 test4 test3 = s~mplevalue

E test [color ="red"] test2 test4 lest3 = s~molevalue

Figura 22.3. El ejemplo DomCreate puede generar diferentes tipos de documentos XML utilizando un DOM.


El segundo ejemplo de creacion de DOM tiene que ver con un conjunto de datos. Hemos aiiadido a1 formulario un componente de conjunto de datos dbExpress (pero habria semido cualquier otro conjunto de datos) y agregado tambien la llamada a1 procedimiento personalizado DataSetToDOM a un boton, de la siguiente manera: DataSetToDOM

('customers',

' c u s t o m e r ' , XMLDoc,

SQLDataSetl);

El procedimiento Da t aSet ToDOM crea el nodo raiz con el texto del primer parametro, coge cada registro del conjunto de datos, define un nodo con el segundo parametro y agrega un subnodo para cada campo del registro utilizando un codigo extremadamente generico: p r o c e d u r e DataSetToDOM (RootName, RecordName: TXMLDocument; DataSet: TDataSet) ; var iNode, iChild: IXMLNode; i: Integer; begin DataSet.Open; Dataset-First;

string; XMLDoc:

// r a i z XMLDoc.DocumentElement

:=

XMLDoc.CreateNode

(RootName);

// afiade d a t o s d e t a b l a w h i l e n o t DataSet .EOF d o begin // a d a d e u n n o d o p a r a c a d a r e g i s t r o iNode : = XMLDoc.DocumentElement.AddChild (RecordName); f o r I : = 0 t o DataSet.FieldCount - 1 d o begin // a f i a d e u n e l e m e n t o p a r a c a d a c a m p o iChild : = iNode.AddChild (DataSet.Fields[i].FieldName); iChild.Text : = DataSet.Fields[i].AsString; end; DataSet.Next; end; DataSet.Close; end;

El codigo anterior utiliza las interfaces de acceso simplificado de DOM que proporciona Borland, que incluyen un nodo AddChi ld que crea el subnodo, y el acceso direct0 a la propiedad Text para definir un nodo hijo con contenido textual. Esta rutina extrae una representacion XML del conjunto de datos, ofreciendo muchas posibilidades para la publicacion Web, como veremos en la seccion sobre XSL. Otra interesante posibilidad es la generacion de documentos XML que describan objetos Delphi. El programa DomCreate tiene un boton que se utiliza para


describir algunas propiedades de un objeto usando, una vez mas, el DOM de bajo nivel : procedure AddAttr (iNode: IDOMNode; Name, Value : string) ; var iAttr: IDOMNode; begin iAttr : = iNode.ownerDocument.createAttribute (name); iAttr.nodeValue : = Value; iNode.attributes.setNamed1tem (iAttr); end; procedure TForml.btnObjectClick(Sender: var iXml: IDOMDocument ; iRoot: IDOMNode; begin // v a c i a e l documento XMLDoc.Active : = False; XMLDoc. XML.Text : = ' '; XMLDoc-Active : = True;

TObject);

// r a i z iXml := XmlDoc. DOMDocument ; iRoot : = iXml.appendChild (iXm1-createElement ( ' B u t t o n l ' ) ) ; / / a l g u n a s p r o p i e d a d e s como a t r i b u t o s ( t a m b i e n p o d r i a n s e r // n o d o s ) AddAttr (iRoot, ' N a m e ' , Buttonl-Name); AddAttr (iRoot, ' C a p t i o n ' , Buttonl .Caption); AddAttr (iRoot, ' F o n t .Name ', Buttonl.Font.Name) ; AddAttr (iRoot, ' L e f t ', IntToStr (Buttonl.Left)) ; AddAttr (iRoot, ' H i n t ', Buttonl .Hint); / / m u e s t r a XML e n u n memo Memol.Lines : = XmlDoc.XML; end;

Desde luego, seria mas interesante disponer de una tecnica generica capaz de guardar las propiedades de cada componente de Delphi (u objeto permanente, para ser mas precisos), recorriendo de manera recursiva 10s subobjetos permanentes e indicando 10s nombres de 10s componentes a 10s que se hace referencia. Esto es lo que hace el procedimiento C o m p o n e n t T o D O M , que utiliza la informacion RTTI bajo nivel proporcionada por la unidad TypInfo e incluye la extraccion de la lista de propiedades de componentes. Una vez mas, el programa utiliza las interfaces XML simplificadas de Delphi: procedure ComponentToDOM var nProps, i: Integer; PropList: PPropList;

(iNode: IXmlNode; Comp: TPersistent);


Value : Variant ; newNode: IXmlNode; begin // o b t i e n e l a lista d e p r o p i e d a d e s nProps : = GetTypeData ( C ~ m p . C l a s s I n f o ) ~ . P r o p C o u n t ; GetMem (PropList, nProps * SizeOf (Pointer)) ; try GetPropInfos (Comp.ClassInfo, PropList) ; for i : = 0 to nProps - 1 do begin Value : = GetPropValue (Comp, PropList [i] .Name) ; NewNode := iNode .Addchild (PropList [i] .Name) ; NewNode.Text : = Value; i f (PropList [i] . PropTypeA .Kind = tkclass) and (Value <> 0 ) then i f TObject (Integer (Value)) is TComponent then NewNode .Text : = TComponent (Integer (Value)) .Name else / / TPersistent p e r 0 n o TComponent: recursive ComponentToDOM (newNode, TOb ject (Integer(Value)) as TPersistent) ; end ; finally FreeMem (PropList); end; end;

Las siguientes dos lineas de codigo, disparan la creacion del documento XML (que se muestra en la figura 22.4): XMLDoc.DocumentE1ement : = XMLDoc.CreateNode(SelffC1assName); ComponentToDOM (XMLDoc.DocumentElement, Self) ;

Interfaces de enlace de datos XML Trabajar con DOM para acceder o generar un documento es bastante tedioso, ya que en lugar de utilizar un acceso logico a 10s datos se nos obliga a utilizar la informacion de posicion. Ademas, manipular series de nodos repetidos de distintos tipos posibles, no es nada sencillo (como en el ejemplo XML del listado 22.1, que describe libros). Ademas, utilizando el DOM podemos crear cualquier documento bien formado, per0 (a menos que utilicemos un DOM con validacion) podemos aiiadir subnodos a cualquier nodo, acabando con documentos casi inutiles, ya que el resto del mundo sera incapaz de manejarlos. Para solucionar estos problemas, Borland ha aiiadido a Delphi un XML Data Binding Wizard, que es capaz de examinar un documento XML o una definicion de un documento (un esquema, un DTD [definicion de tipo de documento] u otro tipo de definicion) y generar un conjunto de interfaces para manipular el documento. Estas interfaces son especificas a1 documento y su estructura y nos permiten disponer de un codigo mas legible, pero son bastante menos genericas en


cuanto a 10s tipos de documentos que podemos manipular con ellas (y esto es mas positivo de lo que podria parecer en primera instancia).

1 TFolml Name = Fmml Lell = 192 Top = 107 Wdh= He~gh!= 412 HorzScrollBar Range 97 VertScrollBar = 20260720 Act~veConlrol=btnRTTl B~DlMode= bdLellToR~ghl Capl~on= DomCrealc Cl~enlHerghl = 385 CfienlWdh = 563 Color = -16777201 Cnnslranls = 20255360

5n

H

a

-

-

FOIM

Charset = 1 Color = 16777208 He~ght= 11

., .... ,,"".-.*-A

'1

-

Figura 22.4. El XML generado para describir el formulario del programa DomCreate. Fijese en que las propiedades de 10s tipos de clase estan mas expandidas.

El asistente XML Data Binding Wizard se activa utilizando el icono correspondiente de la primera pagina del cuadro de dialogo New Items del IDE, o haciendo doble clic directamente sobre el componente XMLDocument. (Es extrafio que el comando correspondiente no este en el menu local del componente). Despues de una pagina en la que seleccionaremos un archivo de entrada, este asistente muestra graficamente la estructura del documento, como se puede ver en la figura 22.5 para el archivo XML de muestra del listado 22.1. En esta pagina es donde nombramos cada entidad de las interfaces generadas, en caso de que no nos gusten 10s que el asistente proporciona de manera predeterminada. Incluso podemos cambiar las reglas utilizadas por el asistente para generar 10s nombres (una flexibilidad especial que no estaria ma1 en otras partes del IDE de Delphi). La pagina final nos ofrece una vista previa de las interfaces generadas y ofrece opciones para generar 10s esquemas y otros archivos de definicion. Para el archivo XML de muestra con 10s nombres de autores, el XML Data Binding Wizard genera una interfaz para el nodo raiz, dos interfaces para las listas de elementos de 10s dos tipos distintos de nodos (libros y libros electronicos), y dos interfaces mas para 10s elementos de cada uno de estos tipos.


6~

booksType texi U Q book

O tale -.

P Generate B

.

--

i i

Figura 22.5. t l aslstente XML Data Blndlng Wlzard de Delphi puede anallzar la estructura de un documento o un esquema (u otra definicion de documento) para crear un conjunto de interfaces para un acceso mas simple y direct0 a 10s datos DOM.

Veamos a continuacion unos fragmentos del codigo generado, disponible en la unidad XmlIntfDefinition del ejemplo Xml I n t e r face: tYPe IXMLBooksType = interface (IXMLNode) [ ' {C9A9PB63-47ED-dP27-8ABA-E71P30BA7Pll) ' 1 )

( Property Accessors

function Get-Text: WideString; function Get-Book: IXMLBookTypeList; function Get-Ebook: IXMLEbookTypeList; procedure Set-Text(Va1ue: Widestring); ( Methods 6 Properties

)

property Text: WideString read Get-Text write Set-Text; property Book: IXMLBookTypeList read Get-Book; property Ebook: IXMLEbookTypeList read Get-Ebook; end; IXMLBookTypeList = interface(IXMLNodeCo11ection) [ ' {3449E8C4-3222-47B8-B2B2-38EE504 79OB6) ' 1 ( Methods 6 Properties

)

function Add: IXMLBookType; function Insert (const Index: Integer) : IXMLBookType; function Get-Item(1ndex: Integer): IXMLBookType; property Items [Index: Integer] : IXMLBookType read Get-Item; default; end; IXMLBookType = interface ( IXMLNode) [ ' {26BF5CS1-9247-4DlA-8584-24AE68969935) )

( Property Accessors

function Get-Title: WideString;

'J


f u n c t i o n Get-Author: IXMLString-List; p r o c e d u r e Set-Title (Value: WideString) ; { Methods & Properties } p r o p e r t y Title: WideString r e a d Get-Title w r i t e Set-Title; property Author: IXMLString-List r e a d G e t A u t h o r ; end:

Para cada interfaz, el XML Data Binding Wizard genera tambien una clase de implementacion que proporciona el codigo para 10s metodos de la interfaz, convirtiendo 10s consultas en llamadas DOM. La unidad incluye tres funciones de inicializacion, que pueden devolver la interfaz del nodo raiz desde un documento cargado en un componente XMLDocument (o un componente que proporcione una interfaz IXMLDocument generica), o devolverla desde un archivo, o crear un DOM completamente nuevo: f u n c t i o n Getbooks(Doc: IXMLDocument) : IXMLBooksType; f u n c t i o n Loadbooks(const FileName: WideString): IXMLBooksType; f u n c t i o n Newbooks: IXMLBooksType;

Despues de generar estas interfaces utilizando el asistente en el ejemplo Xml Interface,hemos repetido el codigo de acceso a1 documento XML que es similar a1 del ejemplo XmlDomTree per0 mas facil de escribir (y leer). Por ejemplo, podemos obtener el atributo del nodo raiz escribiendo simplemente: procedure TForml .btnAttrClick (Sender: TObject) ; var Books: IXMLBooksType; begin Books : = Getbooks (XmlDocumentl) ; ShowMessage (Books.Text) ; end;

Puede ser incluso mas sencillo si recuerda que mientras se escribe este codigo, la funcion Code Insight de Delphi puede ayudar listando las propiedades disponibles de cada nodo, gracias a que el analizador sintactico puede leer las definiciones de la interfaz (aunque no entienda el formato de un documento XML generico). Para acceder a un nodo de una de estas sublistas, escribiremos una de las siguientes sentencias (posiblemente la segunda, con la propiedad de matriz predeterminada) : Books.Book. Items [I] .Title Books .Book [l] .Title

// completo // mds simple

Podemos utilizar un codigo igualmente simplificado para generar nuevos do, cumentos o aiiadir elementos nuevos, gracias a1 metodo personalizado ~ d dque esta disponible en cada interfaz basada en una lista. Si no disponemos de una estructura predefinida para el documento XML, como en 10s ejemplos basados en un conjunto de datos y RTTI de la demostracion anterior, no podremos utilizar este enfoque.


Validation y esquemas El asistente XML Data Binding Wizard puede trabajar a partir de esquemas ya existentes o generar un esquema para un documento XML (e incluso guardarlo en un archivo con la extension .XDB). Un documento XML describe algunos datos, per0 para compartir estos datos entre empresas, tiene que adherirse a alguna estructura previamente acordada. Un esquema es una definicion de documento contra la que se puede comprobar la correccion de un documento, una operacion que suele llamarse validacion. El primer (y mas difundido) tipo de validacion disponible para XML usaba las definiciones de tipo de documento (Document Type Definitions, DTD). Estos documentos describen la estructura del XML per0 no pueden definir 10s posibles contenidos de cada nodo. Ademas, 10s DTD no son documentos XML ellos mismo, sino que usan una notacion diferente y algo extraiia. A finales del aiio 2000, el W3c aprobo el primer borrador oficial de 10s esquemas XML ya disponibles en una version incompatible llamada XML-Data dentro del DOM de Microsoft). Un esquema XML es un documento XML que puede validar tanto la estructura del arb01 XML como el contenido de 10s nodos. Un esquema se basa en el uso y la definicion de tipos de datos simples y complejos, de un mod0 parecido a un lenguaje orientado a objetos. Un esquema define tipos complejos, indicando cada uno de 10s nodos posibles, su secuencia opcional ( s e q u e n c e , a l l ) , el numero de ocurrencias de cada subnodo ( m i n o c c u r s , m a x 0 c c u r s ) y el tipo de datos de cada elemento especifico. Este es el esquema definido por el XML Data Binding Wizard para el archivo de libros de muestra:


Los motores DOM de Microsoft y Apache tienen un buen soporte para 10s esquemas. Otra herramienta que hemos usado para la validation es XML Schema Validator (XSV), un intento de codigo abierto de conseguir un procesador conforme con 10s esquemas, que puede usarse bien directamente a traves de la Web o despues de descargar un ejecutable en linea de comandos (en las paginas sobre XML Schema del W3C se encuentra el enlace a1 sitio Web actual de esta herra-

- - -.- - - -- NOTA: El editor de Delphi soporte la completitud de cbchgo para archives XML gracias a 10s DTD. Si se coloca un ar&vo DTD en el dir&to';io bin de Delphi y se hace referencia a 61 mediante una etiqaeta DOCTYPE, se habilitarh esta caracteristica, que brland no sa~ortadc h n a dfidd.

Uso de la API de SAX La Simple API for XML, o SAX, no crea un arbol para 10s nodos XML, sino que analiza sintacticamente el nodo, disparando eventos para cada nodo, atributo, valor, etc ... Puesto que el documento no se guarda en memoria, la utilizacion del SAX nos permite manejar documentos mucho mas grandes. Este enfoque tambien es muy util para examinar una sola vez un documento o para recuperar informacion especifica. Veamos una lista de 10s eventos activados por SAX: StartDocument y EndDocument para el documento complete. StarElement y EndElement para cada nodo. Charact er s para el testo contenido en 10s nodos

Es bastante comun utilizar una pila para manejar la ruta actual dentro del arbol de nodos, y meter y sacar elementos enly desde la misma para cada evento


Start Element y EndElement . Delphi no incluye soporte especifico para la

interfaz SAX per0 se puede conseguir facilmente importando el soporte XML de Microsoft (la biblioteca MSXML). En particular, para el ejemplo SasDemol, hemos utilizado la version 2 de MSXML, ya que se encuentra muy difundida. Hemos generado una unidad de importacion de biblioteca de tipos de Pascal para la biblioteca de tipos, y la unidad de importacion esta disponible dentro del codigo fuente del programa, per0 necesitamos que la biblioteca COM este registrada en nuestro ordenador para ejecutar el programa con exito.

I -

NOTA:Otro ejcmploVhaciael final delcapituli(Lpgc~rnl) muestra, entis otras cosas, el uso de la API de SAX, incluyendselmotor OpenXml.

I

Para utilizar SAX, tenemos que instalar un controlador de eventos de SAX dentro de un lector SAX, y despues cargar un archivo y analizarlo sinticticamente. Hemos utilizado la interfaz de lectura de SAX proporcionada por MSXML para programadores de VB. La interfaz oficial (C++) tenia unos cuantos errores en su biblioteca de tipos que impedia que Delphi la pudiera importar de forma correcta. En el formulario principal del ejemplo SaxDemo 1 se declara: sax:

IVBSAXXMLReader;

En el metodo Formcreate, la variable sax se inicializa con el objeto COM: sax : = CoSAXXMLReader.Create; sax.ErrorHandler := TMySaxErrorHand1er.Create;

El codigo tambien define un controlador de errores, que es una clase que implementa una interfaz especifica (IVBSAXErrorHandler)con tres metodos a 10s que se llama dependiendo de la gravedad del problema: error, fatalError e ignorablewarning. Simplificando un poco el codigo, el analizador sintactico SAX se activa al llamar a1 metodo parseURL despues de asignarle un controlador de contenido: s a x - C o n t e n t H a n d l e r := TMySaxHandler-Create; s a x .parseURL (filename)

A1 final el codigo se encuentra en la clase TMySaxHandler, que es la que contiene 10s eventos SAX. Ya que en el ejemplo tenemos varios controladores de contenido SAX, hemos escrito una clase basica con el codigo principal y unas cuantas versiones especializadas para el procesamiento especifico. A continuacion veremos el codigo de la clase basica, que implementa l a interfaz I V B S A X C o n t e n t H a n d l e r y la interfaz I D i s p a t c h en que se basa IVBSAXContentHandler: type TMySaxHandler =class (TInterfacedObject, IVBSAXContentHandler)


protected stack: TStringList; public constructor Create; destructor Destroy; override; / / IDispa tch function GetTypeInfoCount(out Count: Integer): HResult; stdcall; function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall; function GetIDsOfNames(const IID: TGUID; Names: Pointer; Namecount, LocaleID: Integer; DispIDs: Pointer) : HResult; stdcall; function Invoke(Disp1D: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer) : HResult; stdcall; / / IVBSAXContentHandler procedure S e t ~ d o c u m e n t L o c a t o r ( c o n s tParaml: IVBSAXLocator); virtual; safecall; procedure startDocument; virtual; safecall; procedure endDocument; virtual; safecall; procedure startPrefixMapping(var strprefix: WideString; var strURI : Widestring) ; virtual; safecall; procedure endPrefixMapping(var strprefix: Widestring); virtual; safecall; procedure startElement(var strNamespaceUR1: WideString; var strLocalName: WideString; var strQName: WideString; const ~Attributes: IVBSAXAttributes); virtual; safecall; procedure endElement(var strNamespaceUR1: WideString; var strLocalName: WideString; var strQName: WideString); virtual; safecall; procedure characters(var strchars: WideString); virtual; safecall ; procedure ignorableWhitespace(var strchars: WideString); virtual; safecall; procedure processingInstruction(var strTarget: WideString; var strData: WideString); virtual; safecall; procedure skippedEntity(var strName: WideString); virtual; safecall; end :

La parte mas interesante es la lista final de 10s eventos SAX. Todo lo que hace esta clase basica es enviar la informacion a un registro cuando el analizador sintactico empieza (startDocument) y finaliza (endDocument) y hace el seguimiento de 10s nodos actuales y 10s nodos padre con una pila: / / TMySaxHandler. s tartElement stack .Add (strLocalName); // TMySaxHandler. endEl ement stack .Delete (stack.Count - 1) ;


La clase TMyS impleSaxHand ler proporciona una implementation real que sobrescribe el evento st art E leme nt lanzado por cualquier nuevo nodo para enviar la posicion actual en el arbol, con la siguiente sentencia: Log.Add

(strLocalName +

' ( ' + stack.ComrnaText +

') ') ;

El segundo metodo de la clase es el evento characters, que se provoca cuando se encuentra un valor de nodo (o un nodo de texto) y envia su contenido (como muestra la figura 22.6): p r o c e d u r e TMySimpleSaxHandler.characters(var WideString) ; var str: WideString; begin inherited; s t r : = Removewhites (strchars); if (str <> " ) then L o g - A d d ('Text: ' + str); end;

List

( '

Paw TkJw

1

pam~w

'

strchars:

I-

s ~ a l ~ ~ o c l m--ent bwkslbooksl book(books.book] lkle(books.book.tille] Texl: La B~bhade Delphi 7 author~books.book.auIhar1

booklbooks,book] blle(books,book,liUe] Text DelphlDeveloper's Handbook author(books,bo&.author] Ten!: Canlu au(hor(books.book.au(hor1 Text: Gooch bwk[books.book] !~tle[books,book,l~Ile] Texl. La Bibl~ade Delphi 6 aulhor(books,book,aulhor] Text. Canlu bmk[books,bwk] Mle(bwks.book,lillej Ted: DelphiCOM Programrring aulho@ooks.book,auIhor] Texl Herrnon bwk(books,book] title(books.book.lille) Text Thlnkmg in Ctt aulhor[bwks.bodLw(hal Text: Edtel

Figura 22.6. El registro generado por la lectura de un documento XML con SAX en el ejernplo SaxDemol.

Se trata de una operacion de analisis sintactico generica que afecta a todo el archivo XML. El segundo controlador de contenido SAX derivado se refiere a la estructura especifica del documento XML, extrayendo solamente nodos de un tip0 determinado. Concretamente, el programa busca 10s nodos de tipo title. Cuando


un nodo es de este tipo (en s t a r t E l e m e n t ) , la clase activa la variable booleana i s b o o k . El valor de texto del nodo se tiene en cuenta solo inmediatamente despues de encontrar un nodo de este tipo: procedure TMyBooksListSaxHandler.startElement(var strNamespaceUR1, strLocalName, strQName: Widestring; const ~ A t t r i b u t e s : IVBSAXAttributes) ; begin inherited; isbook := (strLocalName = 'title ') ; end : procedure T ~ y B o o k s L i s t S a x H a n d l e r . c h a r a c t e r s ( v a r strchars: Widestring) ; var str: string; begin inherited; if isbook then begin s t r : = Removewhites (strchars); if (str <> 'I) then Log.Add (stack.CommaText + ': ' + s t r ) ; end ; end ;

Proyeccion de XML con transformaciones Hay una tecnica mas en Delphi que podemos utilizar para manejar algunos documentos XML: podemos crear una transformacion, para traducir el XML de un documento generico a1 formato nativo que utiliza un componenteC l i e n t D a t a S e t cuando guarda datos en un archivo XML de MyBase. De la misma manera, y en sentido inverso, otra transformacion puede convertir un conjunto de datos disponible en un ClientDataSet (a traves del componente D a t a s e t p r o v i d e r ) , en un archivo XML con un formato (o esquema) determinado. Delphi incluye un asistente que genera estas transformaciones, llamado XML Mapping Tool o XML Mapper, a1 que se accede desde el menu Tools del IDE o que puede ejecutarse como una aplicacion independiente. El XML Mapper, que muestra la figura 22.7, es un asistente en tiempo de diseiio que nos ayuda con la definicion de las reglas de transformacion entre 10s nodos de un documento XML generico y 10s campos del paquete de datos del ClientDataSet. La ventana del XML Mapper tiene tres zonas: A la izquierda esta la seccion del documento XML: Muestra la informacion sobre la estructura del documento XML (y sus datos, si la casilla de activacion correspondiente esta activada) en la Document View o en un esquema XML en la Schema View, segun la solapa que seleccionemos.


Tmrh-mmon E w ~ ~ o ~ m d c v ~ u ~ l - ~ B d r ( , d a a lrri~d

J

Figura 22.7. El XML Mapper muestra 10s dos extremos de una transforrnac~onpara definir la proyeccion entre ellos (con las reglas indicadas en la parte central).

A la derecha se encuentra la seccion del paquete de datos: Muestra la informacion acerca de 10s metadatos en el paquete de datos, bien en la Field View (indicando la estructura del conjunto de datos) o en la Datapacket View (dandonos informacion sobre la estructura XML). Fi.jese en que el XML Mapper tambien puede abrir archivos en el formato original de ClientDataSet.

La parte central d e la ventana se utiliza para la proyeccion: Contiene a su vez dos paginas: Mapping, donde podemos ver las correspondencias entre 10s elementos seleccionados en ambos lados que formaran parte de la proyeccion; y Node Properties, donde podemos modificar 10s tipos de datos y otros detalles de cada posible proyeccion. La pagina Mapping del panel central alberga tambien el menu de metodo abreviado que se utiliza para generar la transformacion. El resto de 10s paneles y vistas tienen menus locales especificos, utilizados para realizar diversas acciones (ademas de unos cuantos comandos en el menu principal). Podemos utilizar XML Mapper para proyectar un esquema existente (o estraerlo a partir de un documento) sobre un paquete de datos nuevo, de un paquete de datos existente a un nuevo esquema o documento, o de un paquete de datos ekistente a un documento XML ya existente (si es razonable una cierta correspondencia). Ademas de convertir 10s datos de un archivo XML a un paquete de datos, tambien podemos convertirlos en un paquete delta del ClientDataSet. Esta tecnica es muy util para fusionar un documento con una tabla, como si un usuario hubiera insertado 10s registros modificados de la tabla. Concretamente, podemos transformar un documento XML en un paquete delta para modificar, borrar o insertar registros.


El resultado de usar el XML Mapper es uno o mas archivos de transformacion, cada uno de 10s cuales representa una conversion en sentido unico (necesitamos a1 menos dos archivos para hacer la conversion en ambos sentidos). Estos archivos de transformacion se utilizan en tiempo de diseiio y de ejecucion por 10s componentes XMLTransform, XMLTransformProvider y XMLTransformClient. A mod0 de ejemplo, hemos intentado abrir el documento XML de 10s libros, que tiene una estructura que no se corresponde facilmente con una tabla, ya que contiene dos listas de valores de distintos tipos. Despues de abrir el archivo sample. xml en la seccion XML Document, hemos utilizado su menu local para seleccionar todos sus elementos (Select All) y para crear el paquete de datos (Create Datapacket From XML). Esta operacion hace que el panel derecho se rellene automaticamente con el paquete de datos y la parte central con la transformacion propuesta. Tambien podemos ver inmediatamente su efecto en un programa de muestra haciendo clic sobre el boton Create and Test Transformation. Esto abre una aplicacion generica que permite cargar un documento en el conjunto de datos usando la transformacion creada. En este caso en concreto, podemos ver que el XML Mapper genera una tabla con dos campos de conjunto de datos: uno para cada una de las posibles listas de subelementos. Esta era la unica solucion estandar posible ya que las dos sublistas tienen estructuras diferentes, y es la unica solucion que permite editar 10s datos en la DBGrid conectada a1 ClientDataSet y guardarlos de nuevo en un archivo XML, tal y como se muestra en el ejemplo XmlMapping. Basicamente, este programa es un editor basado en Windows para un documento XML complejo. El ejemplo utiliza un componente TransformProvider con dos archivos de transformation aiiadidos para leer un documento XML y para que el ClientDataSet pueda disponer de el. Como sugiere su nombre, este componente es un proveedor de conjuntos de datos. Para construir la interfaz de usuario, no hemos conectado directamente el ClientDataSet a una cuadricula, ya que contiene un unico registro con un campo de texto y dos conjuntos de datos detallados. Por ello, hemos aiiadido a1 programa dos componentes ClientDataSet mas enlazados con 10s campos del conjunto de datos y conectados con 10s dos controles DBGrid. Probablemente sea mas facil entender esto si echamos un vistazo a la definicion de 10s componentes no visuales en el codigo fuente DFM en el siguiente fragmento, y a su salida en la figura 22.8. object XMLTransformProviderl: TXMLTransformProvider TransforrnRead.TransformationFile = ' B o o k s D e f a u l t . x t r f TransformWrite.TransformationFile = 'BooksDefau1tToXml.xtr' XMLDataFile = 'Sanple.xml ' end object ClientDataSetl: TClientDataSet ProviderName = 'XMLTransformProviderl ' object ClientDataSetltext: TStringField object ClientDataSetlbook: TDataSetField object ClientDataSetlebook: TDataSetField end


o b j e c t ClientDataSet2: TClientDataSet DataSetField = ClientDataSetlbook end o b j e c t ClientDataSet3: TClientDataSet DataSetField = ClientDataSetlebook end

Delplw COM Pcgfarrunmg

Harmon

Thinkinp m C t t La Bbl~ade Delph 7

Btuce

Can(u

hltg//ww.ma~cocantu.~m Cantu

II

Figura 22.8. El ejemplo XmlMapping utiliza un componente TransformProvider para permitir la edicion de un documento XML complejo dentro de varios componentes ClientDataSet.

Este programa no solo permite editar 10s datos de las diferentes sublistas de nodos dentro de las cuadriculas, sin0 tambien modificarlos, borrarlos o aiiadir nuevos registros. Cuando aplicamos 10s cambios al conjunto de datos (haciendo clic sobre el boton Save, que llama a ~pplyupdates),el proveedor de transformaciones guarda una version actualizada del archivo en el disco. Como metodo alternativo, tambien podemos crear transformaciones que proyecten solo determinadas partes del documento XML sobre un conjunto de datos. Como ejemplo, puede consultarse el archivo Booksonly .x t r que se encuentra en la carpeta del ejemplo XmlMapping. El documento XML modificado que generara tendra una estructura y contenido distintos del original, incluyendo solo la parte que se ha seleccionado. Por eso, puede ser util para ver 10s datos per0 no para editarlos.

Una transformacion puede utilizarse para coger una tabla de una base de datos o el resultado de una consulta y producir un archivo XML con un formato mas


legible que el que nos proporciona por defect0 el mecanismo de permanencia de ClientDataSet. Para construir el ejemplo MapTable, hemos colocado un componente SimpleDataSet de dbExpress en un formulario y le hemos conectado un DataSetProvider y un ClientDataSet a1 proveedor. Despues de abrir la tabla y el conjunto de datos de cliente, hemos guardado su contenido en un archivo XML. El siguiente paso ha sido abrir el XML Mapper, cargar el archivo del paquete de datos en el, seleccionar todos 10s nodos del paquete de datos (con el comando Select All de su menu local) y llamar a1 comando Create XML From Datapacket.

En el siguiente cuadro de dialogo, aceptamos las proyecciones predeterminadas de 10s nombres para 10s campos y solo cambiamos el nombre sugerido para 10s nodos de registro (ROW) por algo mas legible (Customer). Si probamos ahora la transformacion, el XML Mapper mostrara el contenido del documento XML resultante en una vista de arb01 personalizada Una vez que hemos guardado el archivo de transformacion, podemos reanudar el desarrollo del programa, eliminando el ClientDataSet y aiiadiendo un Datasource y una DBGrid (para que un usuario pueda editar 10s datos en una DBGrid antes de transformarlos), y un componente XMLTransformClient. Este componente tiene conectado el archivo de transformacion, per0 no un archivo XML. En lugar de eso, hace referencia a 10s datos a traves del proveedor. A1 hacer clic sobre el boton, veremos el documento XML dentro de un campo de memo (despues de darle formato) en lugar de guardarlo en un archivo, algo que podemos hacer llamando a1 metodo GetDataAsXml (aunque el archivo de ayuda no resulta muy claro sobre el uso de este metodo): procedure TForrnl.btnMapClick(Sender: TObject); begin Mernol.Lines.Text : = ForrnatXmlData(XMLTransf0rmC1ientl.GetDataAsXml(")); end;

Este es el unico codigo del programa que podemos ver en tiempo de ejecucion en la figura 22.9. El conjunto de datos original puede verse en la DBGrid, y el documento XML resultante en el control de memo que se encuentra bajo la cuadricula. La aplicacion dispone de un codigo mucho mas sencillo que el que hemos utilizado en el ejemplo DomCreate para generar un documento XML parecido, per0 requiere la definition de la transformacion en tiempo de diseiio. El ejemplo DomCreate podria trabajar en tiempo de ejecucion sobre cualquier conjunto de datos, sin necesidad de una conexion a una tabla especifica ya es un codigo bastante generico. En teoria, podemos producir proyecciones dinamicas similares utilizando 10s eventos del componente generico XMLTransform, per0 parece mas sencillo usar el enfoque basado en DOM ya comentado. Ademas, la llamada a FormatXmlData produce una salida mas agradable per0 ralentiza el programa, ya que implica la carga del XML en un DOM.


I

1231 Unirco 1351 S~ghlD i w 1354 Cayman Diverr World Llnhded 1356 Tom Swyer D i n g Cedre 1 3 0 Blw Jack Aqua Center 1384 VIP Divers Club

.............. .........

.

Suhe 310

.

..

.

PO BoxZ-547 1 Neptune Lane PO Box 541 632.1 T hid Frydenhq 23-73 PaddnglonL a m 32 Main St.

.-...

....... ........,...Map

...

-,

..........

.-

.

....

..

- -:

O x m l vefs~on="l0"b < Docwnent~ <Cuttomef> ~Custl~lo>t221 ;ICustNo) <Compary)Kaua~D~veShoppe</Cornpany> cAddrl>4.976 Sugarloaf Hwyc/Addrl> cAd&2>Smte 103</Addr2>

Figura 22.9. El ejemplo MapTable genera un documento XML a partir de una tabla de base de datos rnediante un archivo de transforrnacion personalizado.

XML e lnternet Express Una vez que hemos definido la estructura de un documento XML, podemos desear permitir que 10s usuarios vean y editen 10s datos en una aplicacion de Windows o a traves de la Web. Este segundo supuesto es bastante interesante ya que Delphi proporciona un soporte especifico para ello. Delphi 5 ya incluia una arquitectura llamada Internet Espress, que ahora forma parte de la plataforma WebSnap. WebSnap ofrece tambien soporte para XSL, del que hablaremos mas adelante. En el capitulo 16, ya hemos hablado del desarrollo de aplicaciones de DataSnap. Internet Espress proporciona un componente de cliente llamado XMLBroker para esta arquitectura, que puede utilizarse en lugar de un conjunto de datos para obtener 10s datos a partir de un programa DataSnap de capa intermedia y ponerlo a disposicion de un tip0 especifico de productor de pagina llamado InetXPageProducer. Podemos utilizar estos componentes en una aplicacion WebBroker estandar o en un programa WebSnap. La idea de Internet Express es escribir una estension de servidor Web que produzca paginas Web conectadas con nuestro servidor DataSnap. La aplicacion personalizada actua como un cliente DataSnap y produce paginas para un navegador cliente. Internet Espress ofrece 10s servicios necesarios para crear facilmente esta aplicacion personalizada. Puede que resulte confuso, pero Internet Express es una arquitectura de cuatro niveles. Estos son: servidor SQL, servidor de aplicacion (el servidor DataSnap),


servidor Web con una aplicacion personalizada y, finalmente, el navegador Web. Podemos colocar 10s componentes de acceso a la base de datos dentro de la misma aplicacion que maneja la peticion HTTP y que genera el resultado HTML, como en una solucion cliente/servidor. Incluso podemos acceder a una base de datos local o a un archivo XML, con una estructura de dos capas (el programa servidor y el navegador). Es decir, Internet Express es una tecnologia para crear clientes basados en un navegador, lo que nos permite enviar, junto con el HTML, todo el conjunto de datos a1 ordenador cliente. Tambien enviamos algo de codigo JavaScript para poder manipular el XML y mostrarlo dentro de la interfaz de usuario definida por el codigo HTML. El codigo JavaScript es lo que hace posible que el navegador pueda mostrar 10s datos e incluso manipularlos.

El componente XMLBroker Internet Express utiliza diversas tecnologias para conseguir este resultado. Convierte 10s paquetes de datos DataSnap a1 formato XML para que el programa pueda insertar estos datos en la pagina HTML para su manipulation Web en el cliente. Realmente, el paquete de datos delta tambien se representa como XML. El componente XMLBroker lleva a cabo estas operaciones, maneja el XML y proporciona 10s datos a 10s nuevos componentes JavaScript. A1 igual que el ClientDataSet, el XMLBroker tiene: Una propiedad MaxRecords: Sirve para indicar el numero de registros que se aiiaden a una sola pagina. Una propiedad Params: Se utiliza para albergar 10s parametros que 10s componentes reenviaran a la consulta remota a traves del proveedor. Una propiedad WebDispatch: Sirve para indicar la consulta actualizada a la que responde el broker.

El InetXPageProducer permite generar visualmente 10s formularios HTML a partir de 10s conjuntos de datos, de una forma similar a1 desarrollo de una interfaz de usuario AdapterPageProducer. En realidad, la arquitectura de Internet Express, las interfaces internas que utiliza y parte de su editor IDE pueden ser considerados como el progenitor de la arquitectura WebSnap. Con la notable diferencia de generar guiones que se ejecutan del lado del servidor y del cliente, ambos proporcionan un editor para colocar componentes visuales y generar estos guiones. Cabe destacar que el antiguo Internet Express se orienta mas a XML que el mas reciente WebSnap.

I

-

-

--

TRUCO: Otra caracteristica comun del InetXPageProducer y el AdapterPageProducer es el soporte para hojas de estilo en cascada (CSS).

-,


h

I

-.

~ s t o cornponent&~tienen s las propiedyad8- st yi&-e$ ~ i i para e definir el CSS y cada elemento visual time una propiedad 3 t y l e Rule que puede usarse para seleccioaar el nombre del &lo. -

'

I

Soporte de JavaScript Para producir potentes operaciones de edicion en el lado del cliente, el InetXPageProducer utiliza un codigo y unos componentes JavaScript especiales. Delphi incluye una biblioteca bastante extensa de JavaScript, que el navegador tiene que descargar. Es un proceso algo fastidioso, per0 es la unica manera de que la interfaz del navegador (que se basa en codigo HTML dinamico), sea lo suficientemente buena como para soportar restricciones de campos y otras reglas de negocio similares. Esto seria totalmente imposible con el HTML simple. Los archivos de JavaScript proporcionados por Borland, que deberian hacerse disponibles en la pagina Web que albergue la aplicacion, son 10s siguientes:

Analizador sintactico XML compatible con DOM (para navegadores que carezcan de soporte nativo DOM).

Xmldom. j s

I x m l d b . js

Clases JavaScript para 10s controles HTML.

Xm1disp.j~

I

.

I

Clases JavaScript para enlace de datos XML con controles HTML.

Xrnlerrdisp js

Clases para arreglar errores.

Xrn1Show.j~

Funciones JavaScript para mostrar datos y paquetes delta (cuya finalidad es la depuraci6n).

I

Normalmente, las paginas HTML generadas por Internet Express incluyen referencias a estos archivos JavaScript, como en:

Podemos personalizar el codigo JavaScript aiiadiendo directamente codigo a las paginas HTML, o creando nuevos componentes de Delphi escritos para encajar con la arquitectura de Internet Express que produce el codigo JavaScript (posiblemente junto con codigo HTML). Como ejemplo, la clase TPromptQueryButton de muestra de InetXCustom genera el siguiente codigo HTML y JavaScript:


<script language=javascript type="text/javascript"> function PromptSetField (input, msg) ( v a r v = prompt (msg); i f ( V == null I I v == " " ) return false; input. value = v return true ; 1 var QueryForm3 = document.forms['QueryForm3']; </script> <input type=button value="Prornpt onclick="if (PromptSetField(PromptResult, 'Enter some t e x t \ n r ) ) QueryForm3. submit ( ) ;">

..."

TRUCOt Los mriipooentjq dcionales '&muestia.de ENetXCustom sew de graa ayuda si tenaaos ktFncibn de u k Inkmet Express. E& c(mponentcn, estiin dirponibles en la Carpeta \ o dho s \ M i d a s \ ~ n t e r n e t ~ x ~ r e~ s st\~ e ttom. ~ ~ Siga u s 1% dew* instrucciones del ar'chivo readma. t x t para ins'talar estos ~ ~que Borlands proporciona sin n&im tipo de sopork pmo que penniten afUadir muchias wra@terist.icas a Ias aplicaciones &bm&Eqress ccin hpxpdlo esfhrzo adicid. Para utilizar esta arquitectura no necesitamos nada especial en el cliente, ya que puede usarse cualquier navegador que entienda el estandar HTML 4, en cualquier sistema operativo. Sin embargo, el servidor Web debe de ser un servidor de Win32 (esta tecnologia no esta disponible en Kylis) y hay que utilizarlo con bibliotecas DataSnap.

Creacion de un ejemplo Para entender mejor de que hablamos, y como forma de comentar algunos detalles mas tecnicos, vamos a probar sencillo ejemplo llamado IeFirst. Para evitar problemas de configuracion, esta es una aplicacion CGI que accede directamente a un conjunto de datos (en este caso a una tabla local conseguida mediante un componente C l i e n t D a t a S e t ) . Mas tarde veremos como convertir un cliente DataSnap de Windows ya existente en una interfaz que basada en un navegador. Para crear IeFirst, hemos creado una nueva aplicacion CGI y afiadido a su modulo de datos un componente C l i e n t Dataset conectado con un archivo .CDS local y un componente D a t a S e t P r o v i d e r conectado con el conjunto de datos. El paso siguiente es aiiadir un componente XMLBroker y conectarlo con el proveedor: object ClientDataSetl: TClientDataSet FileName = 'C: \Archives d e programa \Archives comunes \Borland Shared\Data \employee.cds '

.


end o b j e c t DataSetProviderl: TDataSetProvider

DataSet = ClientDataSetl end o b j e c t XMLBrokerl: TXMLBroker

ProviderName = 'Da taSetProvider1 ' WebDispatch.MethodType = mtAny WebDispatch. PathInf o = 'XMLBrokerl ' ReconcileProducer = PageProducerl OnGetResponse = XMLBrokerlGetResponse end

Se necesita la propiedad ReconcileProducer para mostrar un mensaje de error adecuado en caso de conflict0 de actualizacion. Uno de 10s programas de ejemplo de Delphi incluye un codigo personalizado, pero, para este caso, simplemente hemos conectado un componente Pageproducer tradicional con un mensaje de error HTML generico. Despues de preparar el XMLBroker, podemos aiiadir un InetXPageProducer al modulo de datos Web. Este componente tiene un esqueleto HTML estandar, que hemos personalizado para aiiadir un titulo, sin modificar las etiquetas especiales: <HTML><HEAD> <title>IeFirst</title> </HEAD><BODY> <hl>Internet Express First Demo (IeFirst . exe)</hl> <#INCLUDES><#STYLES><#WARNINGS><#FORMS><#SCRIPT> </BODY>

Las etiquetas especiales se expanden automaticamente mediante 10s archivos JavaScript del directorio especificado en la propiedad Include Pat hURL. Es necesario establecer esta propiedad para que haga referencia al directorio del servidor Web donde residen estos archivos. Podemos encontrarlos en el directorio \ D e l p h i 7 \ S o u r c e \ W e b M i d a s . Las cinco etiquetas tiene el siguiente efecto:

<#INCLUDES>

Genera las instrucciones para incluir las bibliotecas JavaScript.

<#STYLES>

Aiiade la hoja de definicion de estilo incrustada.

<#WARNINGS>

Se utiliza en tiempo de diseiio para mostrar 10s errores en el editor InetXPageProducer.

<#FORMS>

Genera el codigo HTML producido por 10s componentes de la pagina Web. Aiiade un bloque de JavaScript utilizado para iniciar el guion del lado del cliente.


NOTA: El componente InetXPageProducer tambien maneja unas cuantas etiquetas internas mas. <#BODYELEMENTS> se corresponde con las cinco etiauetas dantilla aredefinida. <#COMPONENT Name=WebCom~o.. - .-x - - ---- de - - la r ----------nentName> forma parte del codigo HTML generado utilizado para declarar 10s componentes generados visualmente. < D A T A P A C K E T -.. ' m -w -m o .Ke r =-m o .Ke m.a m e > se sustltuye con el coaigo AML ael paquete de datos. ~

---

. . A

- - - -

-

-

--

.I

-

-

-

~

I.

--

-

. , 1 ' *

-

~

1

1

-

.-

Para personalizar el HTML resultante dcl InetXPageProducer, podemos utilizar su editor. que vuelve a scr parecido a1 editor de guiones de servidor de WebSnap. Haciendo dobIe clic sobre el componente InetXPageProducer,Delphi abre una x n t a n a como la que muestra la figura 22.10 (con la configuracion final del ejemplo). En este editor podemos crear estructuras complejas partiendo de un formulario de consulta. un formulario de datos o un grupo generic0 de estructura. En el formulario de datos de nuestro ejemplo, hemos aiiadido dos componentes DataGr id y DataNavigator sin personalizarlos (operacion que se puede llevar a cabo aiiadiendo botones hijo, columnas y otros objetos que sustituyan completamente a 10s predeterminados).

-

at?,* -

-

-

-

-

-

InelXPagePraducerl ErnpFlo DataForrnl LaslNarne DalaNavqalor F1r~tNarne

L

A

E:

Salary SlalusColurnnl

Internet Express First Demo (IeFirst.exe)

Figura 22.10. El editor de InetXPageProducer nos permite crear visualmente complejos formularios HTML de una forma parecida al Adapterpageproducer.


El codigo DFM para el InetXPageProducer y sus componentes internos en el ejemplo se muestra a continuacion. Se pueden ver las configuraciones principales, ademas de algunas limitadas personalizaciones graficas: InetXPageProducerl: TInetXPageProducer IncludePathURL = ' / j ssource/ ' HTMLDoc.Strings = ( . . . ) o b j e c t DataForml: TDataForm o b j e c t DataNavigatorl: TDataNavigator XMLComponent = DataGridl Custom = 'align="center" '

object

end o b j e c t DataGridl: TDataGrid

XMLBroker = XMLBrokerl DisplayRows = 5 TableAttributes.BgCo1or = 'Silver' TableAttributes.CellSpacing = 0 TableAttributes.Cel1Padding = 2 HeadingAttributes.BgCo1or = 'Aqua' o b j e c t EmpNo: TTextColumn... o b j e c t LastName: TTextColumn. . . o b j e c t FirstName: TTextColumn... o b j e c t PhoneExt : TTextColumn. . o b j e c t HireDate: TTextColumn. .. o b j e c t Salary: TTextColumn.. . o b j e c t StatusColumnl: TStatusColumn...

.

end end end

El valor de estos componentes esta en el codigo HTML (y JavaScript) que generan y que podemos ver previamente a1 seleccionar la pestaiia H T M L del editor de InetXPageProducer. Veamos a continuacion un parte de las definiciones en el HTML para 10s botones, el encabezado de la cuadricula de datos y una de sus celdas: / / botones <table align="centerV> <tr><td colspan="2"> <input type="buttonW value=" 1 < " onclick='if (xml-ready) DataGridl-Disp. first ( ) ; ' > <input t ype="buttonW value="<< " onclick='if (xml-ready) DataGridl-Disp.pgup();'>

... / / titulo de cuadricula de datos


<tr> / / u n a celda d e datos <td><div> <input type="textW name="EmpNo" size="lO" onfocus='if(xml~ready)DataGridlonfocus='ifoDataGridl_Disp.xfoDi~p.~focus(this);' onkeydown='if (xml-ready) D a tG r i d l o n k e y d o w n = ' i f o D a tG r i d l _ Ds p . k D i ~ p . keys (this); I > </div></td>

...

Despues de preparar el generador de HTML, podemos volver a1 modulo de datos Web, aiiadirle una accion y conectarla con el InetXPageProducer mediante la propiedad P r o d u c e r . Esto deberia bastar para que el funcione a travts-de un navegador, como muestra la fig& 22.1 1 .

programs

1

Internet Express First Demo (IeFirst.exe)

lelson

IRoberio .

'oung

-

--

-

l~ruce

:uao

Figura 22.11. La aplicacion IeFirst envia al navegador algunos componentes HTML, un documento XML complete, y codigo JavaScript para mostrar 10s datos en 10s componentes visuales

Si miramos en el archivo HTML recibido por el navegador, encontraremos la tabla mencionada en la definition anterior, algo de codigo JavaScript y 10s datos de la base de datos en el formato XML del paquete de datos. Estos datos 10s controla el XMLBroker y se 10s pasa a1 componente productor para insertarlos en el archivo HTML. El numero de registros enviados al cliente depende del XMLBroker y no del numero de lineas de la cuadricula. Despues de que se envien 10s datos XML a1 navegador, podemos usar 10s botones del componente navegador para movernos por ellos sin necesidad de acceder a1 servidor para buscar mas. Esto difiere bastante del comportamiento de WebSnap. No queremos decir que un enfoque sea mejor que otro, sino que depende del tip0 de aplicacion que vayamos a construir.


A1 mismo tiempo, las clases JavaScript del sistema permiten que el usuario introduzca datos nuevos, siguiendo las reglas impuestas por el codigo JavaScript que conectado a 10s eventos HTML dinamicos. De manera predeterminada, la cuadricula tiene una columna adicional con un asterisco que indica que registros se han modificado. Los datos de actualizacion se agrupan en un paquete de datos XML en el navegador y se envian de vuelta al sewidor cuando el usuario hace clic sobre el boton Apply Updates. A partir de aqui, el navegador activa la accion especificada por la propiedad WebDispat h . Pat hInf o del XMLBroker. No hay necesidad de exportar esta accion desde el modulo de datos Web, ya que es una operacion automatica (aunque podemos desactivarla si establecemos WebDispath. Enable como False). El XMLBroker aplica 10s cambios al servidor, devolviendo el contenido a1 proveedor conectado a la propiedad Re conc i 1e Provider (o lanzando una excepcion si esta propiedad no esta definida). Si todo funciona bien, el XMLBroker redirige el control a la pagina principal que contiene 10s datos. Sin embargo, hemos experimentado algunos problemas con esta tecnica, por lo que el ejemplo IeFirst controla el evento OnGetResponse, indicando que se trata de una vista actualizada: procedure TWebModulel.XMLBrokerlGetResponse(Sender: TObject; Request: TWebRequest; Response: TWebResponse; v a r Handled: Boolean) ; begin Response .Content : = ' < h l > U p d a t e d < / h l > < p >' + 1netXPageProducerl.Content; Handled : = True; end;

Uso de XSLT Otra posibilidad para generar un codigo HTML partiendo de un documento XML es usar el lenguaje de hojas de estilo extensible (Extensible Stylesheet Language, XSL) o, para ser mas precisos, su subconjunto XSL Transformations (XSLT). El objetivo de de XSLT es transformar un documento XML en otro documento, generalmente un documento XML. Uno de 10s usos mas frecuentes de la tecnologia es convertir un documento XML en un documento XHTML para enviarlo a un navegador desde un servidor Web. Otra interesante tecnologia relacionada es XSL-FO (XSL Formatting Objects), que puede usarse para convertir un documento XML en un documento PDF u otro tipo de documento con formato. Un documento XSLT es un documento XML bien formado. La estructura de un archivo XSLT necesita un nodo raiz como el siguiente:


El contenido del archivo XSLT se basa en una o mas plantillas (o reglas o funciones) que procesara el motor. Su nodo es xsl : template,normalmente con un atributo match. En el caso mas sencillo, una plantilla funciona sobre nodos con un nombre determinado; se invoca la plantilla pasandole uno o mas nodos con una expresion XPath:

El punto de partida para esta operacion es una plantilla que procesa el nodo raiz, que puede ser la unica plantilla del archivo XSLT. En las plantillas, se puede encontrar cualquier otro comando, como la extraccion de un valor desde un documento XML (xsl :value-of select), sentencias de bucle (xsl : for-each), expresiones condicionales (xs1 : if,xs 1 :choose), peticiones de ordenacion (xs1 : sort) y peticiones de numeracion (xsl : number) por mencionar solo algunos comandos XSLT comunes.

Uso de XPath XSLT usa otras tecnologias XML, sobre todo XPath para identificar partes de documentos. XPath define un conjunto de reglas para encontrar uno o mas nodos dentro de un documento. Estas reglas se basan en una estructura de lineas de ruta del nodo dentro del arb01 XML. De esta manera, la /books/book identifica cualquier nodo book bajo la raiz de documento books.XPath utiliza unos simbo10s especiales para identificar a 10s nodos: Un asterisco (*) significa cualquier nodo; por ejemplo book/* indica cualquier subnodo bajo el nodo book. Un punto ( .) significa el nodo actual. El simbolo de barra

(

I

)

significa alternativas como en book I ebook.

Una doble barra inclinada ( / / ) significa cualquier ruta. / /title nos indica todos 10s nodos title, cualesquiera que sean sus nodos padre y books/ /author nos indica cualquier nodo author bajo un nodo books sin tener en cuenta 10s nodos intermedios. El signo de arroba o at ( @ ) indica un atributo en lugar de un nodo, como en el caso hipotetico de author/@lastname. Los corchetes cuadrados ( [ y ] ) pueden usarse para escoger solo 10s nodos o atributos que contengan un valor dado. Por ejemplo, author [ @name= "marco"] seleccionaria todos 10s autores con un atributo de nombre (name) dado (en este caso, marco). Hay muchos casos mas, per0 esta pequeiia introduccion a las reglas de XPath ayudaran con el comienzo y a comprender 10s ejemplos que siguen. Un documento XSLT es un documento XML que trabaja sobre la estructura de un documento


XML origen y que genera como salida otro documento XML, como un documento XHTML que podemos ver en un navegador Web.

NOTA: Entre 10s procesadores XSLT m h usados se incluyen MS-XML, Xalan del proyecto Apache XML (xml. apache. org) y Xt basado en Java de James Clarke. En Delphi tambih se puede usar el motor XSLT, incluido en XML Partner Pro de Turbopower (www.turbopower.com).

XSLT en la practica Vamos a analizar un par de ejemplos. Como punto de partida, deberiamos estudiar el propio estandar XSL y centrarnos en su activacion desde una aplicacion Delphi. Como prueba inicial, podemos conectar directamente un archivo XSL con un a r c h i ~ ~XML. o Cuando cargamos en archivo XML en lnternet Explorer veremos la transformacion en XHTML resultante. La conexion se indica en la cabecera del documento XML con un comando como el siguiente: Esto es lo que hemos hecho en el archivo samplelembedded . xml disponible en la carpeta XslEmbed.El XSL relacionado incluye varios fragmentos de XSL que no tenemos espacio para comentar en detalle. Por ejemplo, coge la lista completa de autores o filtra un grupo especifico de ellos con el siguiente codigo:

Se usa codigo mas complejo para extraer nodos solo cuando se encuentra presente un valor especifico en un subnodo o atributo, sin tener en cuenta 10s nodos de mayor nivel. El siguiente fragment0 de XSL tambien tiene una sentencia i f y produce un atributo en el nodo resultante, como un mod0 de crear un hipervinculo href en el codigo HTML: < h 3 > M a r c o 1 s works <ul>

(books

+ ebooks)</h3>


XSLT con WebSnap Dentro del codigo de un programa, existe la posibilidad de ejecutar el metodo Transf ormNode de un nodo DOM, pasandole otro DOM que contenga el documento XSL. Sin embargo, en lugar de utilizar este enfoque de bajo nivel, podemos crear un ejemplo basado en XSL con la ayuda del WebSnap. Podemos crear una nueva aplicacion de WebSnap (En este caso, hemos creado un programa CGI llamado XslCust) y escoger un componente XSLPageProducer para su pagina principal, para permitir que Delphi nos ayude a comenzar con el codigo de la aplicacion. Delphi incluye tambien un archivo XSL esqueleto para manipular un paquete de datos ClientDataSet y aiiade muchas nuevas vistas a1 editor. El texto XSL sustituye al archivo HTML; la pagina XML Tree muestra 10s datos, si hay alguno; la pagina XSL Tree muestra el XSL dentro de un componente ActiveX de Internet Explorer; la pagina HTML Result muestra el codigo producido por la transformacion y, finalmente, la pagina Preview muestra lo que el usuario vera en un navegador.

TRUCO: En Delphi 7, el editor proporciona la completitud de codigo para XSLT, que hace que la edicion de este tipo de c6digo en el editor sea tan potente como en algunos sofisticados editores especificos para XML. Para que funcione este ejemplo, debemos proporcionar algunos datos al componente XSLPageProducer a traves de su propiedad XMLData. Esta propiedad puede conectarse a un XMLDocument o directamente a un componente XMLBroker, como hemos hecho en este caso. El XMLBroker toma 10s datos de un proveedor conectado a una tabla local, enlazada con el clasico componente de tabla customer. c d s de DBDEMOS. El efecto es que, con el siguiente XSL generado con Delphi, se consigue (incluso en tiempo de diseiio), el resultado que muestra la figura 22.12:


txs1:template match="ROWDATA/ROW"> txs1:variable name="fieldDefs" select="//METADATA/FIELDS"/> <xsl:variable name="currentRow" select="current()"/> <tr> <xsl:for-each select="$fieldDefs/FIELD"> <td> <xsl:value-of select="$currentRow/ @ *[ n a m e ( ) =current ( ) /@attrname] " / > < b r / > </td> </xsl:for-each> </tr> </xsl:template>

.

-

--.

-

- -

.

NOTA: La ~lantillaXSL esthdar se ha &mPIiado desde D e l ~ h6. i va aue . las versiones originales no tenian en cuenta 10s campos nulos omitidos en el T

GXLGLISIULI a 1

c;uu~gu A

~

.

uLn g u GLI ~aLUUL Durlanu LUIUGIGIL~;~: y S

U

~

~

de sus sugerencias han sido incorporadas a la plantilla. Este codigo genera una tabla HTML consistente en la expansion de 10s metadatos de campo y datos de fila. Los campos se usan para generar el encabezado de la

S


tabla, con una celda < t h > para cada entrada de una sola fila. Los datos de fila se usan para rellenar el resto de las filas de la tabla. No basta con tomar el valor de cada atributo (select=" @ * "); ya que 10s atributos pueden no existir. Por este motivo, la lista de campos y la fila actual se guardan en dos variables: despues, para cada campo, el codigo XSL extrae el valor de un elemento de fila que tenga un nombre de atributo (@* [name( ) = . .) que se corresponda con el nombre del campo actual guardado en su atributo attrnnme (eattrname). Este codigo no resulta nada sencillo, per0 es un mod0 compacto y adaptable para analizar divcrsas partes de un documento XML a1 mismo tiempo.

.

j Procedures .JUser

Tom

--

I

39 1

Insert

~ H T M ~ ~ P ~ Tree,&~~ ~ ~ ~Tree/ ~ , $ M. L

'igura 22.12. El resultado de una transforrnacion XSL generada por el cornponente XSLPageProducer en el ejernplo XslCust.

Transformaciones XSL directas con DOM Usar el XSLPageProducer puede ser util, per0 generar varias paginas basadas en 10s mismos datos simplemente para poder manejar estilos XSL posiblemente distintos con WebSnap, no parece el mejor metodo. Hemos creado una aplicacion CGI simple, llamada CdsXstl que puede transformar un paquete de datos ClientDataSet en diferentes tipos de HTML, segun el nombre del archivo XSL que se haya pasado como parametro. La ventaja es que ahora se pueden modificar 10s archivos XSL existentes y aiiadir nuevos archivos XSL sin tener que volver a compilar el programa. Para conseguir la transformacion XSL, el programa carga 10s archivos XML y XSL en sendos componentes XMLDocument, llamados XmlDom y XslDom. Despues invoca el metodo transformNode del documento XML, pasando el documento XSL como parametro y rellenando un tercer componente XMLDocument, llamado HtmlDom:


procedure TOb j ect;

TWebModulel.WebModulelWebActionItemlAction(Sender:

Request: TWebRequest; Response: TWebResponse; var Handled: Boolean) ; var

xslfile, xslfolder: string; attr: IDOMAttr; begin // abre el conjunto de datos del cliente y carga su XML en un // DOM

ClientDataSetl.0pen; XmlDom.Xml.Text : = ClientDataSetl.XMLData; XmlDom.Active := True; // carga el archivo xsl solicitado xslfile : = Request.QueryFields.Va1ues ['style']; i f xslfile = " then xslfile : = 'customer.xsl '; xslfolder : = ExtractFilePath (ParamStr (0)) + 'xsl\'; i f FileExists (xslfolder + xslfile) then xslDom.LoadFromFile (xslfolder + xslfile) else r a i s e Exception.Create('Missing

file: '

+

xslfolder +

xslfile) ; XSLDom.Active : = True; i f xslfile = 'single.xsl ' then begin

attr : = xslDom.DOMDocument.createAttribute('select'); attr.value : = '//ROW[@CustNo="' + Request. QueryFields .Values [ 'id'] + ' " I '; xslDom.DOMDocument.getElementsByTagName ('xs1:applytemplates ' ) . item[O].attributes.setNamedItem(attr); end; / / redliza la transformation HTMLDom.Active : = True; xmlDom.DocumentElement.transformNode (xslDom.DocumentElement, HTMLDom);

Response.Content

:=

HTMLDom.XML.Text;

end:

El codigo usa el DOM para modificar el documento XSL para mostrar un unico registro, aiiadiendo la sentencia XPath para seleccionar el registro indicado por el campo de consulta i d . Este i d se aiiade a1 hipervinculo gracias a1 XSL con la lista de 10s registros, pero no vamos a listar mas archivos XSL, ya que podemos obtenerlos en la subcarpeta XSL de la carpeta de este ejemplo y analizarlos con mayor detenimiento.

ADVERTENCIA: Para ejecutar este programa, hay que colocar 10s archivos XSL en una carpeta llamada XSL dentro de aquella en que se encuentre la Carpeta de 10s guiones de este capitdo. Para desplegar e m s archivos en


~

t

i

n

t

a

,

-

~

w

k

i

g que'e&e l .

el dombn

de la carpeia XSL a partir del mmbbredeJpn$ramq dispuitible en el primer pariunetio ep b e a de comandos (yaque el orrjeto%pplication definido en la unidad Forms no es accesible por media & iiiia aplicacib CGI).

-

Procesamiento de grandes documentos XML Como ya se ha visto, suelen existir muchas tecnicas distintas para realizar la misma tarea con XML. En la mayoria de 10s casos se puede escoger cualquier solution, teniendo en mente el objetivo de escribir un codigo menor y mas facil de mantener. Pero cuando se necesita procesar un gran numero de documentos XML o documentos XML muy grandes, hay que tener en cuenta la eficiencia. Comentar la teoria por si misma no es muy util, asi que crearemos un ejemplo que pueda utilizarse (y modificarse) para probar distintas soluciones. El ejemplo se llama LargeXml, y trata un campo especifico: llevar datos desde una base de datos a un archivo XML y en sentido inverso. El ejemplo puede abrir un conjunto de datos (usando dbExpress) y replicar despues 10s datos varias veces en un ClientDataSet que resida en memoria. La estructura del ClientDataSet en memoria se duplica a partir del componente de acceso a 10s datos:

Despues de utilizar un grupo de botones de radio para definir la cantidad de datos que se desea procesar (algunas opciones pueden requerir minutos en un ordenador lento); 10s datos se duplican mediante este codigo: while ClientDataSet1.RecordCount < nCount do begin SimpleDataSet1.RecNo : = Random (SimpleDataSet1.RecordCount) + 1; ClientDataSetl-Insert; ClientDataSetl. Fields [0].AsInteger := Random (10000); for I : = 1 to SimpleDataSetl.Fie1dCount - 1 do ClientDataSetl.Fields [i].AsString : = SimpleDataSetl.Fields [i].AsString; ClientDataSetl-Post; end:

De un ClientDataSet a un documento XML Ahora que el programa tiene en memoria un gran conjunto de datos, proporciona tres modos distintos de guardar el conjunto de datos en un archivo. El primer0 es guardar directamente la propiedad XMLData del ClientDataSet en un


archivo, consiguiendo asi un documento basado en atributos. Probablemente no sea este el formato que se desee, por lo que la segunda solucion es aplicar una transformacion de XmlMapper mediante un componente XMLTransf ormClient . La tercera solucion implica procesar directamente el conjunto de datos y escribir cada registro en un archivo: procedure TForml.btnSaveCustomClick(Sender: TObject); var str: TFileStream; s: string; i: Integer; begin str : = TFileStream.Create ( 'data3.xmZ ', fmcreate) ; try ClientDataSet1.First; s : = '<?xmZ version="l 0" s tandalone="yes " ?><employee> '; str .Write (s[1] , Length (s)) ;

.

while not ClientDataSetl.EOF do begin :=

' I .

for i : = 0 to ClientDataSetl.Fie1dCount - 1 do s : = s + MakeXmlstr (ClientDataSetl.Fields [i] . FieldName, ClientDataSetl.Fields[i].AsString); s : = MakeXmlStr ( 'employeeData ', s) ; str-Write(s[1] , length (s)) ; ClientDataSet1.Next end; s : = '</employee>'; str.Write (s[1] , length (s)) ; finally str . Free; end; end;

Este codigo utiliza una funcion auxiliar simple per0 eficaz para crear 10s nodos XML: function MakeXmlstr (node, value: string) : string; begin Result : = ' < I + node + ' > I + value + I < / ' + node + end :

' > I ;

Si se ejecuta el programa, se podra ver el tiempo que se tarda en cada operacion, como lo muestra la figura 22.13. Guardar 10s datos del ClientDataSet es el enfoque mas rapido, per0 probablemente no se consiga el efecto deseado. El streaming personalizado es solo ligeramente mas lento, per0 deberia considerarse que este codigo no necesita que 10s datos se lleven en primer lugar a un ClientDataSet, porque se puede aplicar directamente, incluso en un conjunto de datos unidireccional de dbExpress. Deberiamos olvidarnos de utilizar el codigo


basado en el XmlMapper para un conjunto de datos grande, porque es varios cientos de veces mas lento, incluso para un conjunto de datos pequeiio (ni siquiera hemos podido probarlo con un conjunto de datos grande, porque, sencillamente, tarda demasiado). Por ejemplo, 10s 50 milisegundos que necesita un streaming personalizado para un conjunto de datos pequeiios se convierten en m b de 10 segundos cuando usamos la proyeccion, y el resultado es muy parecido.

313 Robert

E

--

4747 LesLe

332

G Ion

937

k

6716 161 7 Janet

'

Nelson

_

!-

5 -

Bddwn

2

s-'

,7 -

__ 1410 -

Johnson h",bM

.

-

-

'2-L.

1

+I

Figura 22.13. El ejemplo LargeXml en accion.

De un documento XML a un ClientDataSet Una vez que se tiene un documento XML grande, conseguido por un programa (como en este caso) o a partir de una fuente externa, es necesario procesarlo. Como ya se ha visto, el soporte de XmlMapper es demasiado lento, por lo que solo quedan tres alternativas: una transformacion XSL, un SAX o un DOM. Las transformaciones XSL probablemente Sean lo suficientemente rapidas, per0 en este ejemplo hemos abierto el documento con un SAX, ya que se trata del enfoque mas rapido y no necesita mucho codigo. El programa tambien puede cargar un documento en un DOM, per0 no hemos escrito el codigo necesario para recorrer el DOM y guardar 10s datos de nuevo en un ClientDataSet. En ambos casos, hemos probado el motor OpenXml contra el motor MSXML. Esto permite ver las dos soluciones SAX como comparacion, porque (lamentablemente) el codigo es ligeramente distinto. A mod0 de resumen de 10s resultados: usar la SAX de MSXML es ligeramente mas rapido que usar el SAX de OpenXML (la diferencia esta en torno a un 20 por ciento), mientras que la carga en el DOM significa una amplia ventaja a favor de MSXML. El codigo del SAX de MSXML utiliza la misma arquitectura ya comentada en el ejemplo SaxDemol, por lo que


aqui solo vamos a mostrar el codigo de 10s controladores que se utilizan. Como puede verse, a1 comienzo de un ejemplo employeeData se inserta un nuevo registro, que se envia cuando se cierra el mismo nodo. Los nodos de menor nivel se aiiaden como campos al registro actual. Este es el codigo: procedure TMyDataSaxHandler.startElement(var strNamespaceUR1, strLocalName, strQName: WideString; const ~Attributes: IVBSAXAttributes); begin inherited; i f strLocalName = 'employeeDatat then Forml.clientdataset2.Insert; strcurrent : = ' I ; end; procedure TMyDataSaxHandler.characters(var strchars: WideString) ; begin inherited; strcurrent : = strcurrent + Removewhites (strchars); end; procedure TMyDataSaxHandler.endElement(var strNamespaceUR1, strLocalName, strQName : WideString) ; begin i f strLocalName = employeeData ' then Forml.clientdataset2.Post; if stack.Count > 2 then Forml.ClientDataSet2.FieldByName (strLocalName).Asstring .= strcurrent; inherited; end;

El codigo para 10s controladores de eventos en la version OpenXml es parecido. Todo lo que cambia son las interfaces de 10s metodos y 10s nombres de 10s parametros: type TDataSaxHandler = class (TXmlStandardHandler) protected stack: TStringList; strcurrent: string; public constructor Create(aowner: TComponent); override; function endElement(const sender: TXmlCustomProcessorAgent; const locator: TdomStandardLocator; namespaceUR1, tagName: widestring): TXmlParserError; override; function PCDATA(const sender: TXmlCustomProcessorAgent; const locator: TdomStandardLocator; data: widestring): TXmlParserError; override;


f u n c t i o n s t a r t E l e m e n t ( c o n s t sender: TXmlCustomProcessorAgent; const locator: TdomStandardLocator; namespaceURI, t a g N a m e : widestring; attributes: TdomNameValueList) : TXmlParserError; override; d e s t r u c t o r Destroy; override; end;

Tambien es mas dificil invocar el motor de SAX, como se muestra en el codigo siguiente (del que hemos eliminado el codigo de creacion del conjunto de datos, la medida del tiempo y el registro): p r o c e d u r e TForml.btnReadSaxOpenClick(Sender: TObject); var agent: TXmlStandardProcessorAgent; reader: TXmlStandardDocReader; filename: string; begin L o g : = memoLog. L i n e s ; filename := ExtractFilePath (Application.Exename) + ' d a t a 3 . xml '; agent : = TXmlStandardProcessorAgent.Create(ni1); reader:= TXmlStandardDocReader.Create ( n i l ) ; try reader.NextHandler : = TDataSaxHandler.Create (nil); / / our custom c l a s s agent.reader : = reader; agent.processFile(filename, filename); finally agent.free; reader.free; end; end;


Servicios Web y SOAP

De entre todas las caracteristicas mas recientes de Delphi, una sobresale por encima de todas: el soporte para servicios Web incluido en el producto. El hecho de que lo tratemos hacia el final del libro no tiene nada que ver con su importancia, sino solamente con el logico desarrollo del texto y con el hecho de que no es el mejor punto de partida para comprender la programacion en Delphi. El tema de 10s servicios Web es muy amplio e implica varias tecnologias y estindares de negocio. Como siempre, nos centraremos en la implementacion subyacente de Delphi y en la parte tecnica de 10s servicios Web, en lugar de analizar el panorama global y las implicaciones empresariales. Este capitulo tambien es importante porque Delphi 7 aiiade una gran cantidad de potencia a la implementacion de servicios Web que ofrecia Delphi 6, incluyendo soporte para adjuntos, cabeceras personalizadas y muchas cosas mas. Veremos como crear un cliente y un servidor de servicios Web, y tambien como transportar datos de una base de datos sobre SOAP empleando la tecnologia DataSnap. Este capitulo trata 10s siguientes temas: Servicios Web. SOAPyWSDL. DataSnap sobre SOAP


Manejo de adjuntos. UDDI.

Servicios Web Esta tecnologia en rapida expansion tiene el potencial de cambiar la forma en la que Internet funciona para las empresas. Explorar una pagina Web para hacer un pedido esta bien para un unico usuario (las conocidas aplicaciones B2C o aplicaciones de empresa a consumidor), pero no para una empresa (las aplicaciones B2B de empresa a empresa). Si queremos comprar unos cuantos libros, visitar el sitio Web de un vendedor de libros y escribir nuestros pedidos estara bien, pero si nuestro negocio es una libreria y queremos hacer cientos de pedidos a1 dia, este metodo esta lejos de ser el mas adecuado, en particular si tenemos un programa que nos ayude a hacer el seguimiento de ventas y determinar nuevos pedidos. Seria ridiculo tomar la salida de este programa y volver a escribirla en otra aplicacion. La idea de 10s s e ~ i c i o Web s es solventar este problema: el programa utilizado para el seguimiento de ventas, puede crear automaticamente una consulta y enviarla a un servicio Web, el cual, devuelve inmediatamente la informacion sobre el pedido. El siguiente paso podria ser una consulta sobre el numero de envio. A continuacion, el programa puede utilizar otro servicio Web para hacer el seguimiento del envio hasta que llegue a su destino y asi poder decirle a1 cliente cuanto tardara en llegar. Cuando llegue el envio, el programa puede enviar una notificacion SMS o mediante un busca a las personas que tengan solicitudes pendientes, emitir una orden de pago con el servicio Web de un banco, ... y podriamos continuar pero creemos que ya se ha captado la idea. Los servicios Web estan pensados para la interoperabilidad informatics a1 igual que la Web y el correo electronico, estan pensados para la comunicacion entre personas.

SOAP y WSDL Los servicios Web son posibles gracias a1 Simple Object Access Protocol (SOAP). Creado sobre el protocolo HTTP estandar para que un servidor Web pueda manejar las consultas SOAP y que 10s paquetes de datos puedan pasar a traves de cortafuegos. SOAP define una notacion basada en XML para solicitar la ejecucion de un metodo por parte de un objeto en el servidor, pasandole 10s parametros. Mediante otra notacion se define el formato de la respuesta.

I

ROTA: SOAP h e desarrollado originalmente por DevelopMentor (la cornpafiia de entrenamiento de Don Box, el experto en COM)y Microsofi, para superar la debilidad del uso de DCOM en s e ~ d o r e Web. s Sometido a1


r

W para f su es C n i m u c h &qxis&~ lo .Mgieron. co.aparticular empujc de IBM. Es muy pronto pQa aaber si se producid una eatandarizacih real para que 10s programas de software de Miqrosoft, IBM, Sun, Oracle y muchos otros interaden realmeate o si algunas de cstas marcas tratarh de promover una version privada del esthdar. En cualquier caso, SOAP es s61o una de Ias piedras angulares de la arquiWhm .NETde Microsoft, asi como de las platafonnas aduales de Sun y Omclc.

1

SOAP reemplazara, a1 menos entre ordenadores diferentes, el uso de COM. Del mismo modo, la definicion de un servicio SOAP en el formato Web Services Description Language (WSDL) sustituira a1 IDL y las bibliotecas de tipos utilizadas por COM y COM+. Los documentos WSDL son otro tipo de documentos XML que proporcionan la definicion de metadatos de una consulta SOAP. Cuando obtenemos un archivo en este formato (generalmente publicado para definir un servicio), podremos crear un programa para llamarlo. De manera especifica. Delphi proporciona una proyeccion bidireccional especifico entre WSDL y las interfaces. Esto significa que podemos coger un archivo WSDL y generar una interfaz para el. Podemos incluso crear un programa de cliente que incluya las consultas de SOAP mediante estas interfaces y utilizar un componente especial de Delphi que nos permita convertir las consultas de la interfaz local en llamadas SOAP (a menos que queramos generar manualmente el XML necesario para una consulta SOAP). En sentido inverso, podemos definir una interfaz (o utilizar una existente) y permitir que un componente Delphi genere una descripcion WSDL para ella. Otro componente nos proporciona una proyeccion de SOAP a Pascal para que a1 insertar este componente y un objeto que implemente la interfaz dentro del programa de servidor, consigamos en unos minutos poner en marcha un servicio Web.

Traducciones BabelFish Como primer ejemplo del uso de un servicio Web, vamos a crear un cliente para el servicio de traduccion BabelFish ofrecido por AltaVista. Se puede encontrar este y muchos otros servicios para su experimentacion en el sitio Web de XMethods (~vw.xmethods.com). Despues de descargar la descripcion WSDL de este servicio del sitio de XMethods (disponible tambien entre 10s archivos de codigo fuente para este capitulo), hemos invocado a1 Web Services Importer de Delphi en la pagina Web Services del cuadro de dialog0 New Items y hemos seleccionado el archivo. El asistente permite acceder a una vista previa de la estructura del servicio (vCase la figura 23.1) y generar las interfaces en lenguaje Delphi apropiadas en una unidad como la siguiente (con muchos de 10s comentarios eliminados):


klsDL Conocrmnlx E8 . Bab$F~shSewm I'(DZDB671Z-BBBO-lDA6-8DBC-8A445595ABOC)'l function BabelF~sh(const trmslatlonrodo: Wldel end; f m c c x o n CetBabelFlsMortType(UseWSDL- Boolean-Systs

- - .- - -

1

I

--

lil

--An-

--

I

Figura 23.1. El WSDL Import Wizard en accion. unit ~abelFishService; interface uses InvokeRegistry, SOAPHTTPClient, Types, XSBuiltIns; type BabelFishPortType = interface(IInvokab1e) [ ' (D2DB6712-EBEO-lDA6-8DEC-8A445595AEOC)' 1 function BabelFish(const translationmode: WideString; const sourcedata: WideString): WideString; stdcall; end; function GetBabelFishPortType(UseWSDL: Boolean=System.False; Addr: string="; HTTPRIO: THTTPRIO = nil): BabelFishPortType; implementation

// omitido initialization InvRegistry.RegisterInterface(TypeInfo(BabelFishPortType), ' urn:xmethodsBableFish ', ' ') ; 1nvRegistry.RegisterDefaultSOAPAction (TypeInfo (BabelFishPortType), ' urn:xmethodsBableFish#Babe1Fish ') ; end.

Observe que la interfaz hereda de la interfaz Ilnvokable. Esta interfaz no aiiade nada con respecto a 10s metodos de la interfaz base llnterface de Delphi,


sino que se compila con el indicador utilizado para establecer la generacion RTTI, ( $M+}, como la clase T P e r s i s t e n t . En la seccion de inicializacion puede verse tambien que la interfaz se registra en el registro de invocacion global (o I nvReg i s t r y), pasando la referencia de informacion de tipo del tipo de interfaz.

NOTA: Dis-r de info@n pan$ mtmfaccs q m&mpte d avance tecd&gicq &I imparfslnte relacionado-conla i ~ c a c i l i t SOAP. l No es que proyea$h de ~ C l h .PGcal 6 no'sea M p b m e (es v i a paf'd simplificar el prowso) sino que' d$sponex d.e. i n t k m n a d b RTfl para ma interfaz es lo que realmente hace t p e la arquite&ra sea pdente .j;r~pusta. El tercer elemento de la unidad generada por el WDSL Import Wizard es una funcion global que toma su nombre del servicio, introducida en Delphi 7. Esta funcion ayuda a simplificar el codigo utilizado para llamar a1 servicio Web. La funcion G e t B a b e l F i s h P o r t T y p e devuelve una interfaz del tipo apropiado, que puede usarse para lanzar directamente una Ilamada. Por ejemplo, el siguiente codigo traduce una breve frase de ingles a italiano (corno indica el valor de su. primer parametro, e n -i t ) y la muestra en pantalla: ShowMessage w o r l d ! ' )) ;

(GetBabelFishPortType .BabelFish ( 'en-it'

,

'Hello,

Si se presta atencion a1 codigo de la funcion G e t B a b e l F i s h P o r t T y p e , se vera que crea una componente interno de invocacion de la clase THTTPRIO para procesar la Ilamada. Puede colocarse manualmente este componente en el formulario cliente (corno en el programa de ejemplo) para tener mas control sobre sus diversas variables (y controlar sus eventos). Este componente puede configurarse de dos maneras basicas: puede hacerse referencia a1 archivo o a1 URL WSDL, importarlo, y extraerlo a partir del URL de la llamada SOAP; o puede proporcionarse un URL direct0 para la Ilamada. El ejemplo tiene dos componentes que proporcionan 10s enfoques alternatives (que tienen exactamente el mismo efecto): o b j e c t HTTPRIOI: T H T T P R I O WSDLLocation = 'C:\md6code\23\BdbelFish\Bdbe1FishService.~ml' Service = ' B a b e l F i s h ' Port = ' B a b e l F i s h P o r t ' end o b j e c t HTTPRI02: T H T T P R I O U R L = 'http://services. xrnethods. net :80/perl/sodplite. c g i ' end

Llegados a este punto, poco mas hay que hacer ya. Tenemos informacion sobre el servicio que podemos usar para invocarlo y conocemos 10s tipos de 10s parametros requeridos por el unico metodo disponible tal y como se indican en la interfaz.


Los dos elementos se unen extrayendo la interfaz a que queremos llamar directamente desde el componente HTTPRIO, con una expresion como HTTPRIOl a s Babe 1Fi s hPor tTy p e . Puede parecer sorprendente, per0 es increiblemente simple. Esta es la llamada a1 servicio Web realizada por el ejemplo: EditTarget.Text : = (HTTPRIO1 as BabelFishPortType). BabelFish(ComboBoxType.Text, Editsource-Text);

La salida del programa, como muestra la figura 23.2, permite aprender idiomas (aunque, en este caso, el profesor tiene algunas limitaciones, claro). No hemos repetido este mismo ejemplo con opciones de compra, divisas, pronosticos del tiempo y muchos otros servicios disponibles, porque tendrian todos un aspect0 muy parecido.

ADVERTENCIA: Aunpe la interfaz de servicio Web pmporciona 10s tipos de 10s padmetros, en muclios casos se necesitarb ooakuhar.la documentacion red del servicio para saber q d significan reabmitk 103 valores de 10s parbeti.0~y c6mo 10s interpreti el servicio. El setvhio Web de BabeIFish es un ejempio de esto, ya que ha sido necesario &liar documentacion textual paraenmntrar la lista de las tipos de trst&ccion, disponibles en el programa he muestra en un cuadro combinado.

pzq

I~hs1s a sample message lci an aularnabc llanslal~on

len_de

Ideses !st e m Basp~ebnze~ge lu am automat~scheUbwsel-

i

.--.-.__I

Dud

1

Figura 23.2. Un ejemplo de la traducclon de ingles a alernan conseguida con BabelFish de AltaVista rnediante un serviclo W e b .

Creacion de un servicio Web Si llamar a un servicio Web en Delphi es sencillo, lo mismo podriamos decir del desarrollo de un servicio. Si vamos a la pagina Web Services del cuadro de dialog0 New Item, podemos ver la opcion SOAP Server Application. Seleccionandola, Delphi nos presentara una lista que es bastante parecida a la seleccion de una aplicacion WebBroker. De hecho, el servicio Web tipicamente se alberga en un servidor Web empleando una de las tecnologias de extension de servidores (CGI, ISAPI, modulos Apache, etc. ..) disponibles o el Web App Debugger para


las pruebas iniciales. Despues de completar este paso, Delphi aiiadira tres componentes a1 modulo Web resultante, que no es mas que un modulo Web basico sin adiciones especiales: El componente HTTPSoapDispatcher recibe la consulta Web como lo haria cualquier otro repartidor HTTP. El componente HTT PSoapPasca 1Invo ker realiza la operacion inversa a la del componente HTTPRIO: es capaz de traducir consultas SOAP en llamadas para interfaces Pascal (en lugar de convertir las llamadas a metodos de la interfaz en consultas SOAP). El componente WS DLHTMLPub1ish puede usarse para extraer la definicion WSDL del servicio a partir de las interfaces que soporta, y realiza el papel contrario a1 del Web Services Importer Wizard. Tecnicamente, se trata de otro repartidor HTTP.

Un servicio Web de conversion de divisas Una vez preparado este marco (que tambien podemos crear aiiadiendo 10s tres componentes anteriores a un modulo Web ya existente) podemos comenzar a escribir el servicio. Como ejemplo, vamos a transformar el ejemplo de conversion a1 euro del capitulo 3 en un servicio Web llamado Convertservice. Lo primer0 ha sido aiiadir a1 programa una unidad que defina la interfaz del servicio: tn?e IConvert = interface (IInvokable) [ ' {FFlEAA45-0B94-4630-9A18-E768A91A78E2) ' 1 function Convertcurrency (Source, Dest: string; Amount: Double) : Double; s tdcall ; function ToEuro (Source: string; Amount : Double) : Double; s tdcall ; function FromEuro (Dest: string; Amount: Double): Double; stdcall; function TypesList : string; stdcall; end;

Si definimos una interfaz directamente en el codigo, sin necesidad de utilizar una herramienta como el Type Library Editor, conseguimos una gran ventaja, ya que podemos crear facilmente una interfaz para una clase ya existente sin tener que aprender a utilizar una herramienta especifica para ello. Fijese en que le hemos dado un GUID a la interfaz, como es habitual, y que hemos utilizado la convencion de llamada stdcall,ya que el convertidor de SOAP no soporta la convencion de llamada predefinida, reg ister . En la misma unidad que define la interfaz del servicio, deberiamos ademas registrarlo. Esta operacion sera necesaria tanto en la parte del cliente como en la


del servidor, ya que podremos incluir la unidad de definicion de esta interfaz en ambos. uses

InvokeRegistry;

initialization InvRegistry.RegisterInterface(TypeInfo(1Convert));

Ahora que disponemos de una interfaz que podemos mostrar a1 publico, tenemos que proporcionarle una implementacion. Para ello utilizaremos, una vez mas, el codigo estandar de Delphi (con la ayuda de la clase TInvo kableclass predefinida): type TConvert = c l a s s (TInvokableClass, IConvert) protected f u n c t i o n ConvertCurrency (Source, Dest: string; Amount: Double) : Double; stdcall; f u n c t i o n T o E u r o (Source : string; Amount: Double) : Double; s tdcall; f u n c t i o n F r o m E u r o (Dest: string; Amount: Double) : Double; stdcall; f u n c t i o n TypesList: string; stdcall; end;

La implementacion de estas funciones, que llaman a1 codigo del sistema de conversion a1 euro del capitulo 3, no se cornentan aqui porque tiene poco que ver con el desarrollo del servicio. No obstante, es importante tener en cuenta que esta unidad de implementacion tambien tiene una llamada de registro en su seccion de inicializacion: 1nvRegistry.RegisterInvokableClass

(TConvert);

Publicacion del WSDL A1 registrar la interfaz, se permite que el programa genere una descripcion WSDL. La aplicacion del servicio Web (a partir de la actualization Delphi 6.02) es capaz de mostrar una pagina inicial que describe sus interfaces y 10s detalles de cada interfaz, y devuelve el archivo WSDL. A1 conectar a1 servicio Web mediante un navegador, ser vera algo similar a lo que muestra la figura 23.3.

NOT*: Aunclu6 otr& arcpitccturas de servicios Web proporcionan auto^^^ uu modo de el seretieio%bWd'e el navegador, e m th&aqsuele ~ e ÂŁMA, r 9orque utilizar seryiciqs web'kene sentido en una arquitectQraen la qqs is.ieractbeadiSiqt8s aplicadbneg. Si todo lu t p e se necesita es mostrar datos ed pn aahgkdm. dskria creme un 'BitioWeb.


-

ConvertService Service Info Page ConvertService

-

PortTypes:

Iconvert [WSDL] 0

Convertcurrency

0

ToEuro

0

FromEuro

0

TypesLtst

IWSDLPublish [.=I LI-t; ,711 !h* P 0 r t P p ~ 5p ~ ~ b l ~ b ~ yhl eb dl Servlre ~

WSIL:

0

GetPortTypeLirt

0

GetWSDLForPortType

0

GetTypeSystemsList

0

GetXSDForTypeSystem

Lird t > : ' I S - l n s ~ e r t l n n dnwriwnt ot 5eralces

Figura 23.3. La descripcion del servicio Web ConvertService proporcionada por componentes Delphi.

Esta caracteristica autodescriptiva no estaba disponible en 10s servicios Web creados en Delphi 6 (que solo proporcionaba un listado WSDL a bajo nivel), per0 es bastante sencilla de afiadir (o personalizar). Si se analiza el modulo Web SOAP de Delphi 7, ser vera una accion predefinida con un controlador para el evento OnAct i o n que invoca el siguiente comportamiento predefinido: WSDLHTMLPublishl.ServiceInfo(Sender, Handled) ;

Request,

Response,

Esto es todo lo que hay que hacer para proporcionar esta caracteristica a un servicio Web de Delphi ya esistente que carezca de ella. Para conseguir de forma manual una prestacion similar, hay que llamar a1 registro de invocacion (el objeto global I n v R e g i s t r y ) , con llamadas comoGet I n t e r f a c e E x t e r n a l N a m e y G e t M e t h E x t e r n a l N a m e . Lo que es importante es la capacidad del servicio Web de autodocumentarse para cualquier otro programador o herramienta de programacion, presentando el WSDL.

Creacion de un cliente personalizado Veamos la aplicacion cliente que llama a1 servicio. No necesitamos partir del archivo WSDL, dado que ya tenemos la interfaz Delphi. Ni siquiera es necesario que el formulario tenga el componente HTTPPRIO, el cual se crea en el codigo: private Invoker: THTTPRio;


procedure TForml.FormCreate(Sender: TObject); begin Invoker : = THTTPRio.Create(ni1); 1nvoker.URL : = 'http://localhost/scripts/ConvertService.exe/ soap/iconvert ' ; ConvIntf : = Invoker a s IConvert; end;

Como una alternativa al uso del un archivo WSDL, el componente que invoca a SOAP puede asociarse con un URL. Una vez que se ha realizado esta asociacion y la interfaz necesaria se ha extraido del componente, podemos empezar a escribir el codigo Pascal para invocar a1 servicio, como hemos visto anteriormente. Un usuario puede rellenar 10s dos cuadros combinados, llamando a1 metodo TypesList,que devuelve una lista de las monedas disponibles dentro de una cadena (separada por puntos y coma). Extraeremos esta lista sustituyendo cada punto y coma por un caracter de nueva linea y asignando despues directamente la cadena multilinea a 10s elementos combinados: procedure TForml.Button2Click(Sender: TObject); var TypeNames: string; begin TypeNames : = ConvIntf.TypesList; ComboBoxFrom.1tems.Text : = StringReplace (TypeNames, sLineBreak, [rfReplaceAll]) ; ComboBoxTo. Items : = ComboBoxFrom. Items; end :

I ; ' ,

Despues de seleccionar dos divisas, podemos realizar la conversion con este codigo (la figura 23.4 muestra el resultado): procedure TForml.ButtonlClick(Sender: TObject); begin LabelResu1t.Caption : = Format ('tn', [(ConvIntf.ConvertCurrency( ComboBoxFrom.Text, ComboBoxTo-Text, StrToFloat(EditAmount.Text)))]); end;

-[

"z Fill List DEM

I =I

Figura 23.4. El cliente Convertcaller del servicio Web Convertservice rnuestra 10s rnarcos alemanes necesarios para conseguir muchisimas liras italianas, antes de que el euro lo cambiara todo.

POCOS


Peticion de datos de una base de datos Para este ejemplo, hemos creado un servicio Web (basado en el Web App Debugger) capaz de presentar datos sobre 10s empleados de una empresa. Estos datos de proyecto sobre la tabla EMPLOYEE de la base de datos InterBase de muestra que hemos usado a menudo en este libro. La interfaz Delphi del servicio Web se define en la unidad SoapEmployeeIntf de este modo: type ISoapEmployee = interface (IInvokable) ['{77DOD940-23EC-49A5-9630-ADE0751E3DB3)'] function GetEmployeeNames: string; stdcall; function GetEmployeeData (EmpID: string): string; stdcall; end:

El primer metodo devuelve una lista de 10s nombres de todos 10s empleados de la empresa, y el segundo devuelve 10s detalles de un empleado determinado. La implementacion de esta interfaz se proporciona en la unidad SoapEmployeeImpl con la clase siguiente: type TSoapEmployee = class(TInvokableClass, ISoapEmployee ) public function GetEmployeeNames: string; stdcall; function GetEmployeeData (EmpID: string) : string; s tdcall; end;

La implementacion del servicio Web recae en 10s dos metodos anteriores y algunas funciones auxiliares para gestionar 10s datos XML devueltos. Pero antes de llegar a la parte XML del ejemplo, vamos a analizar brevemente la seccion de acceso a la base de datos.

Acceso a los datos Toda la conectividad y el codigo SQL de este ejemplo se guarda en un modulo de datos independiente. Por supuesto, podriamos haber creado dinamicamente algunos componentes de conexion y de conjuntos de datos en 10s metodos, per0 hacerlo seria contrario a1 enfoque de una herramienta de desarrollo visual como Delphi. El modulo de datos tiene la siguiente estructura: object DataModule3: TDataModule3 object SQLConnection: TSQLConnection ConnectionName = 'IBConnection' DriverName = 'Interbase' Loginprompt = False Params .Strings = // omitido end object dsEmplList: TSQLDataSet ComrnandText = 'select EMP-NO, L A S T-NAME, FIRST-NAME EMPLOYEE '

from


SQLConnection = SQLConnection o b j e c t dsEmplListEMP-NO: TStringField o b j e c t dsEmplListLAST-NAME: TStringField o b j e c t dsEmplListFIRST-NAME: TStringField end o b j e c t dsEmpData: TSQLDataSet ComrnandText = 'select * f r o m E M P L O Y E E where E m p N o = : i d f Params = < item DataType = ftFixedChar Name = 'id' ParamType = ptInput end> SQLConnection = SQLConnection end end

Como puede verse, el modulo de datos tiene dos sentencias SQL en sendos componentes SQLDataSet. La primera se utiliza para obtener el nombre e identificador de cada empleado, y la segunda devuelve el conjunto de datos completo para un empleado dado.

Paso de documentos XML El problema esta en como devolver 10s datos a un programa cliente remoto. En este ejemplo, hemos usado un buen enfoque: devolver documentos XML, en lugar de trabajar con las complejas estructuras de datos de SOAP. (No es facil entender que XML pueda verse como un mecanismo de comunicacion para la invocacion mediante SOAP, junto con el mecanismo de transporte que proporciona HTTP, per0 luego no se utilice para 10s datos que se transmiten. Aun asi, muy pocos servicios Web devuelven documentos XML, por lo que puede que otros programadores no lo tengan asi de claro.) En este ejemplo, el metodo GetEmployeeNames crea un documento XML que contiene una lista de empleados, con sus nombres y apellidos como valores y el identificador en la base de datos como atributo, empleando las dos funciones auxiliares MakeXml S tr (descrita en el ultimo capitulo) y MakeXmlAt tr ibut e (que se muestra aqui): f u n c t i o n TSoapEmployee.GetEmployeeNames: string; var dm: TDataModule3; begin d m : = TDataModule3 .Create ( n i l ) ; try dm.dsEmplList.0pen; Result : = '<employeeList>' + sLineBreak; w h i l e n o t dm.dsEmplList.EOF do begin Result : = Result + ' ' + MakeXmlStr ('employee', dm.dsEmp1ListLASTNAME.AsString + ' ' +


dm.dsEmplListFIRSTNME.AsString, MakeXmlAttribute ( 'id', dm.dsEmplListEMPN0.AsString)) + sLineBreak; dm.dsEmplList.Next; end; Result : = Result + ' < / e m p l o y e e L i s t > '; finally dm. Free; end; end;

function MakeXmlAttribute (attrName, attrvalue: string) : string; begin Result : = attrName + ' = " ' + attrvalue + ""; end ;

En lugar de emplear la generacion manual de XML, podriamos haber empleado el XML Mapper o alguna otra tecnologia, per0 hemos preferido crear directamente XML en forma de cadenas. Usaremos el XML Mapper para procesar 10s datos recibidos en el cliente --

- --

-

- -

--

- -.- -

--

-

.

.

NOTA: Puede que se pregunte por qui crea el programa una nueva instancia del mMulo de datos cada vez. La parte negativa de este enfoaue es oue 6:1 programa establece cada vez un;a nueva conexion con I :una operation bastante lenta); perco la pmte positiva es qu -'----=- - - - relmmnaan --I--: - - - el -1 1 mln nenwn con ilnn ilna a r -s e eiecil------ ne ---- anlicacihn -r------- milltihun -tan concurrentemente dos peticiones a1 servicio Web, se puede utilizar una conexiirn carnpartida a la base de datos, per0 hay que usar componentes de . . . . .- . conjunto ae aatos cusumos para a acceso a aatos. rwrmws aespmar ms conjuntos be dams en el d&go & la fuudb y mantener &lo la conexi6n en el modulo de datos, o tener un mbdulo de datos cornpartido global para la conexion (usado por varias hebras) y una instancia especifica de un segundo modulo de datos albergado por 10s Gaqjuntos be datos para cada llamada a m&odo. 2

--.d-

..--

---------a

.. .

. . - .,

-J---

. .

Prestemos ahora atencion a1 segundo metodo, Get EmployeeData.Utiliza una consulta parametrica y da formato a 10s campos resultantes en nodos XML independientes (mediante otra funcion auxiliar, FieldsToXml): function TSoapEmployee .GetEmployeeData (EmpID: string) : string; var dm: TDataModule3; begin dm : = TDataModule3 .Create (nil); try dm.dsEmpData.ParamByName('ID') .Asstring : = EmpId;


dm.dsEmpData.Open; Result : = FieldsToXml finally dm. Free; end; end;

(

'employee ', dm. dsEmpData) ;

f u n c t i o n FieldsToXml (rootName: string; data: TDataSet): string; var i: Integer; begin Result : = ' < ' + rootName + ' > ' + sLineBreak;; f o r i : = 0 to data.FieldCount - 1 d o Result : = Result + ' ' + MakeXmlStr ( Lowercase (data.Fields [i] FieldName) , data. Fields [i] .Asstring) + sLineBreak; Result : = Result + ' < / I + rootName + ' > ' + sLineBreak;; end;

.

El programa cliente (con proyeccion XML) El paso final para este ejemplo es escribir un programa cliente de prueba. Podemos hacerlo como siempre, importando el archivo WSDL que define el servicio Web. En este caso, tambien tendremos que convertir 10s datos XML que se reciben en algo mas manejable, en particular la lista de empleados que devuelve el metodo Get Emp 1 o ye eName s . Como ya comentamos, hemos usado el XML Mapper de Delphi para convertir la lista de empleados recibida del servicio Web enun conjunto de datos que pueda mostrarse mediante una DBGrid. Para realizar esto, en primer lugar hemos escrito el codigo para recibir el XML con la lista de empleados y copiarlo en un componente de memo y de ahi, a un archivo. Despues, hemos abierto el XML Mapper, cargado el archivo y generado a partir de el la estructura del paquete de datos y el archivo de transformacion. (Puede encontrarse el archivo de transformacion entre el resto de archivos de codigo fuente del ejemplo SoapEmployee.) Para mostrar 10s datos XML dentro de una DBGrid, el programa utiliza un componente XMLTransformProvider, que hace referencia a1 archivo de transformacion. object XMLTransformProviderl: TXMLTransformProvider TransforrnRead.TransformationFile = 'EmplListToDataPacket. xtr' end

El componente Client D a t aSet no esta conectado a1 proveedor, ya que trataria de abrir el archivo de datos XML especificado por la transformacion. En este caso, 10s datos XML no se encuentran en un archivo, si no que se pasan a1 componente tras llamar a1 servicio Web. Por este motivo, el programa lleva directamente en el codigo 10s datos a1 ClientDataSet. procedure TForml.btnGetListClick(Sender: TObject); var


strXml: string; begin

strXml : = Get1SoapEmployee.GetEmployeeNames; strXML : = XMLTransformProvider1.T~an~f0rmRead.Transfor~ML(strXml); ClientDataSetl.XmlData : = strXml; ClientDataSetl.0pen; end ;

Con este codigo, el programa puede mostrar la lista de empleados en una DBGrid, como muestra la figura 23.5. Cuando se consiguen 10s datos de un empleado especifico, el programa extrae el identificador del registro activo desde el ClientDataSet y muestra el XML resultante en un campo de memo: procedure TForml.btnGetDetailsClick(Sender: TObject); begin Memo2.Lines.Text : = GetISoapEmployee.GetEmployeeData( end ;

Nelson Robert Young B w e

Jdnson Leslie Forest Ph1 Weslon K. J. Lee Te~ri Hall Stewart Young Katherine Papadopoules Chris Fisher Pete De Sarra Ropr

Figura 23.5. El programa cliente del ejemplo de servicio Web SoapEmployee.

Depuracion de las cabeceras SOAP Una indicacion final sobre este ejemplo esta relacionada con el uso del Web App Debugger para probar aplicaciones SOAP. Por supuesto, puede ejecutarse el programa servidor desde el IDE de Delphi y depurarlo facilmente, per0 tambien pueden vigilarse las cabeceras SOAP que se pasan a traves de la conexion HTTP. Aunque prestar atencion a SOAP desde esta perspectiva de bajo nivel puede ser algo muy poco sencillo, es el mod0 definitive de comprobar si algo va ma1 con una aplicacion SOAP de cliente o servidor. Como ejemplo, en la figura 23.6 puede verse el registro HTTP de una solicitud SOAP del ultimo ejemplo.


El Web App Dcbugger podria no cstar siempre disponiblc, por eso otra tecnica habitual es controlar 10s eventos del componente HTTPRIO, como hace el ejemplo BabelFishDebug. El formulario del programa time dos componentes de memo en 10s que puede verse la peticion SOAP y la respuesta: p r o c e d u r e TForml.HTTPRIO1BeforeExecute(const String; v a r SOAPRequest: W i d e s t r i n g ) ; begin MemoRequest.Text : = SoapRequest; end:

MethodName:

p r o c e d u r e TForml.HTTPRIO1AfterExecute(const MethodName: SOAPResponse: T S t r e a m ) ; begin S0APResponse.Position : = 0; MemoResponse.Lines.LoadFromStream(S0APResponse); end ;

String;

Content Type fext/xrnl User Agent Baland SOAP 1 2 Host Iocalhosl 1024 Contenc Length 508 C o r m c t ~ o nKeep Alwe Cache Control n o a c h e

thd vewon="1.0"?) t SOAP-ENV:Envelopemlns:S~P~ENV="hll~//schemas.mlsoap.ug/w~en~ebpe~~ m l n s xsd="hltp~//www w3.org12001 M L S c h e m a " mlns HSI-'lafp //www w3 org/2001 /XMLSchemamstance" xmlns SO~.ENC="htfp.//schemas.xmlsoap.o~g/soap/enc~n'~cSOAP~ENV:Body SOAP-ENV encodiylStyle="http://a:hemas.Mnlmap org/soap/encod1ng/"~tNS1:GetErnployeeData xmhs NS 1="run SoapEmployeelnlf.ISoapEmployee"~ tEmplD wxtype-"xsd slrmg">ll c/EmplD>c/NSl GelEmployeeDala>t/SOAP.ENV Body)c/SOAP.ENV Envelope,

Figura 23.6. El registro HTTP del Web App Debugger incluye la peticion SOAP a bajo nivel.

Exponer una clase ya existente como un servicio Web Aunque podria desearse desarrollar un servicio Web desde la nada, en algunos casos puede existir codigo que se quiera hacer accesible. Este proceso no es demasiado complejo, dada la arquitectura abierta de Delphi en este campo. Para probarlo, habra que seguir estos pasos:


1. Crear una aplicacion de servicio Web o aiiadir 10s componentes relacionados a un proyecto WebBroker ya existente. 2. Definir una interfaz que herede de llnvokable y aiiadirle 10s metodos que se desean hacer accesibles en el servicio Web (mediante la convencion de llamada s tdcall). Los metodos seran parecidos a 10s de las clase que se quiera hacer accesible. 3. Definir una nueva clase que herede de la clase que se desea exponer e implementar la interfaz. Los metodos se implementaran llamando a 10s metodos correspondientes de la clase base. 4. Escribir una metodo de creacion de un objeto de la clase de implernentacion para cada vez que lo necesite una peticion SOAP.

Este ultimo paso es el mas complejo. Podria definirse una fabrica y registrarla de esta manera: procedure MyObjFactory begin

(out Obj : TObject) ;

Obj : = TMyImplClass.Create; end ; initialization

InvRegistry.RegisterInvokableClass(TMyImplClass, MyObjFactory);

Sin embargo, este codigo crea un objeto nuevo para cada llamada. Utilizar un unico objeto global seria igual de malo: varios usuarios podrian tratar de usarlo, y si el objeto tiene un estado o sus metodos no son concurrentes, podrian darse problemas. Queda la necesidad de implementar algun tipo de control de sesion, que es una variante del prob!,cma que teniamos con el primer servicio Web que se conectaba a la base de datos.

DataSnap sobre SOAP Ahora que tenemos una idea razonablemente aceptable de como crear un servidor y un cliente SOAP, vamos a ver como utilizar esta tecnologia para crear una aplicacion DataSnap multicapa. Usaremos un Soap Server Data Module para crear el nuevo servicio Web y el componente Soapconnection para conectarle una aplicacion cliente.

Creacion del servidor SOAP DataSnap Vamos a ver en primer lugar el caso del servidor. Accederemos a la pagina Web Services del cuadro de dialogo New Items y usaremos el icono Soap Server Application para crear un nuevo servicio Web, y despues el icono Soap


Server Data Module para aiiadir un modulo de datos de servidor DataSnap a1 servidor SOAP. Hemos hecho esto en el ejemplo SoapDataServer7 (que usa la arquitectura Web App Debugger para poder hacer pruebas). A partir de aqui, todo lo que hay que hacer es escribir un servidor DataSnap normal (o una aplicacion DataSnap de capa intermedia), como se comento en el capitulo 16. En este caso, hemos aiiadido el acceso a InterBase a1 programa mediante dbExpress, con lo que tenemos la siguiente estructura: o b j e c t SoapTestDm: TSoapTestDm object SQLConnectionl: TSQLConnection ConnectionName = ' I B L o c d l ' end object SQLDataSetl: TSQLDataSet SQLConnection = SQLConnectionl CommandText = 'select * f r o m EMPLOYEE' end o b j e c t DataSetProviderl: TDataSetProvider DataSet = SQLDataSetl end end

El modulo de datos creado para un servidor DataSnap basado en SOAP define una interfaz personalizada (para que se le puedan aiiadir metodos) que hereda de IAppServerSOAP, que se define en una interfaz publicada (incluso aunque no herede de Ilnvokable).

para exponer'datos media& SOAP. ~ e l i h 7i ha py$t&do a ese ~ & r predefmido mediante la interfaz beredada lAppSe;iu&fJ~I?. ,que es fbncionalmente identica pero permite que el eistam pueda determinar el tipo de llamada atendiendo a1 nombre de la interfaz. En breve, veremos c6mo llamar a una aplicaci6n antigua desde un cliente creado con Delphi 7, ya que este proceso no es autondtticol . La clase de implementation, TSoapTes t Dm, es el modulo de datos, como en otros servidores DataSnap. Este es el codigo Delphi generado, con el aiiadido del metodo personalizado: type ISampleDataModule = interface(IAppServerS0AP) [ ' {D47A293F-4024-4690-9915-8A68CB273D39) '1 f u n c t i o n GetRecordCount: Integer; stdcall; end; TSampleDataModule = class(TSoapDataModule, ISampleDataModule, IAppServerSOAP, IAppServer)


DataSetProviderl: TDataSetProvider; SQLConnectionl: TSQLConnection; SQLDataSetl: TSQLDataSet; public f u n c t i o n GetRecordCount: Integer; stdcall; end;

El TSoapDataModule base no hereda de T I n v o k a b l e C l a s s . No se trata de un problema siempre que se proporcione un procedimiento de creacion adicional para crear el objeto (que es lo que hace automaticamente la T I n v o k a b l e C l a s s ) y se aiiada el codigo de registro (como ya se comento anteriormente): p r o c e d u r e TSampleDataModuleCreateInstance(out begin obj : = TSampleDataModule .Create (nil); end ;

obj:

TObject);

initialization InvRegistry.RegisterInvokab1eC1ass( TSampleDataModule, TSampleDataModuleCreateInstance); InvRegistry.RegisterInterface(TypeInfo(ISampleDataModu1e));

La aplicacion servidor tambien publica las interfaces IAppServerSOAP e IAppServer, gracias al codigo (breve) de la unidad SOAPMidas. Como comparacion, puede encontrarse el servidor DataSnap de SOAP creado con Delphi 6 en la carpeta SoapDataServer. El ejemplo sigue pudiendose compilar en Delphi 7, y funciona bien, per0 deberian escribirse 10s nuevos programas, con la estructura del nuevo; en la carpeta SoapDataServer7 se encuentra un ejemplo. -.

- -

-

-

-

- - -- -

TRUCO: Las aplicaciones de servicios Web en Delphi 7 pueden incluir mas de un m6dulo de datos SOAP. Para identificar un m6dulo de datos SOAP especifico, utilizaremos la propiedad SOAPServerIID del componente SoapConnection o afiadiremos el nombre de la hterfaz del m6dulo de datos a1 final del URL. El servidor tiene un metodo personalizado (la version para Delphi 6 del programa tambien tenia uno, per0 jamas funciono) que utiliza una consulta con la sentencia SQL s e l e c t count ( * ) f r o m EMPLOYEE. f u n c t i o n TSampleDataModu1e.GetRecordCount: Integer; begin / / l e e e l r e c u e n t o d e r e g i s t r o s m e d i a n t e una consulta SQLDataSet2.0pen; Result : = SQLDataSetZ.Fields[O].AsInteger; SQLDataSet2.Close; end ;


Creacion del cliente SOAP DataSnap Para crear la aplicacion cliente, llamada SoapDataClient7, comenzamos a partir de un programa sencillo y le aiiadimos un componente Soapconnect ion (desde la pagina Web Services de la paleta), conectandolo a1 URL del servicio Web DataSnap y haciendo referencia a la interfaz especifica que queriamos usar: object SoapConnectionl: TSoapConnection URL = ' h t t p : / / l o c a l h o s t :1024/SoapDa t a S e r v e r 7 . s o a p d a t a s e r v e r / ' + ' s o a p / I s a m p l e d a tarnodule ' SOAPServerIID = ' I A p p S e r v e r S O A P - [ C 9 9 F 4 7 3 5 - 0 6 0 2 - 4 9 5 C - 8 C A 2 E53E5A439E61) ' UseSOAPAdapter = False end

Fijese en la ultima propiedad, Use SOAPAdap t er, que indica que trabajamos contra un servidor creado con Delphi 7. Como comparacion, el ejemplo SoapDataClient (de Delphi 6), que utiliza un servidor creado con Delphi 6 y se ha vuelto a compilar con Delphi 7, debe tener establecida esta propiedad como True. Este valor obliga a1 programa a usar la interfaz IAppServer simple en lugar de la nueva interfaz IAppServerSOAP. Desde aqui, todo es como siempre: aiiadir un componente Cl ient Dat aSe t, un Datasource y una DBGrid a1 programa, escoger el unico proveedor disponible para el conjunto de datos cliente y conectar el resto. No es sorprendente que para este ejemplo tan simple la aplicacion cliente tenga poco codigo personalizado: una unica llamada para abrir la conexion cuando se hace clic sobre un boton (para evitar errores de arranque) y una llamada Appl yUpdat e s para enviar 10s cambios de vuelta a la base de datos.

SOAP frente a otras conexion con DataSnap A pesar del aparente parecido de este programa con el resto de 10s programas de cliente y sewidor DataSnap creados en el capitulo 16, existe una diferencia muy importante que hay que resaltar: 10s programas SoapDataServer y SoapDataCliente no utilizan COM para exponer o llamar a la interfaz IApp Server SOAP.Mas bien a1 contrario, las conexiones basadas en sockets y HTTP de DataSnap siguen basandose en objetos COM locales y en el registro del sewidor en el Registro de Windows. Sin embargo, el soporte basado en SOAP nativo permite una solucion completamente personalizada que es independiente de COM y que ofrece muchas mas posibilidades de adaptarse a otros sistemas operativos. (Se puede volver a compilar este sewidor en Kylix, per0 no es asi para 10s programas del capitulo 16.) El programa cliente tambien puede llamar a1 metodo personalizado que hemos aiiadido a1 servidor para devolver el recuento de registros. Este metodo podria usarse en una aplicacion real para mostrar solo un numero limitado de registros


per0 informar a1 usuario del numero de registros que aun no se han descargado desde el servidor. El codigo del cliente para llamar a1 metodo se basa en un componente HTTPRIO adicional: p r o c e d u r e TFormSDC.Button3Click(Sender: TObject); var SoapData: ISampleDataModule; begin SoapData : = HttpRiol a s ISampleDataModule; ShowMessage (IntToStr (SoapData.GetRecordCount)); end;

Manejo de adjuntos Una de las caracteristicas mas importantes que Borland ha afiadido a Delphi 7 es el completo soporte de adjuntos SOAP. Los adjuntos en SOAP permiten enviar datos que no Sean texto XML, como archivos binarios o imagenes. En Delphi, 10s adjuntos se gestionan a traves de flujos. Se puede leer o indicar el tip0 de codification del adjunto, per0 la transformacion de un flujo bruto de bytes hacia y desde una codificacion dada depende del codigo. Aun asi, este proceso no es demasiado complejo, si se tiene en cuenta que Indy incluye unos cuantos componentes de codificacion. Como ejemplo del uso de adjuntos, hemos escrito un programa que reenvia el contenido binario de un ClientDataSet (que tambien alberga imagenes) o una sola de las imagenes. El servidor tiene esta interfaz: tn?e ISoapFish = i n t e r f a c e ( IInvokable) [ ' {4E4C57BF-4AC9-41C2-BB2A-64BCE4 7OD4SO} ' 1 f u n c t i o n GetCds: TSoapAttachment; stdcall; f u n c t i o n GetImage(fishName: s t r i n g ) : TSoapAttachment; s tdcall; end;

La implernentacion del metodo G e t C d s usa un ClientDataSet que hace referencia a la clasica tabla BIOLIFE, crea un flujo en memoria, copia en el 10s datos, y despues adjunta el flujo a1 resultado T S o a p A t t a c h m e n t : function TSoapFish.GetCds: TSoapAttachment; stdcall; var memStr: TMemoryStream; begin Result : = TSoapAttachment-Create; memStr : = TMemoryStream-Create; WebModule2.cdsFish.SaveToStream(MemStr); // binary Result.SetSourceStream (memStr, soReference); end;


En el cliente, hemos preparado un formulario con un componente Client Dataset conectado a una DBGrid y una DBImage. Todo lo que hay

que hacer es conseguir el adjunto SOAP, guardarlo en un flujo temporal en memoria y despues copiar 10s datos desde el flujo de memoria al ClientDataSet local. p r o c e d u r e TForml.btnGetCdsClick(Sender: TObject); var sAtt: TSoapAttachment; memStr: TMemoryStream; begin nRead : = 0; sAtt : = (HttpRiol a s ISoapFish) .GetCds; try memStr : = TMemoryStream.Create; tr Y sAtt SaveToStream(memStr) ; memStr.Position : = 0; ClientDataSetl.LoadFromStream(MemStr); finally memStr. Free; end; finally DeleteFile (sAtt .CacheFile) ; sAtt.Free; end; end ;

.

ADVERTENCIA: De manera predeterminada, 10s adjuntos de SOAP recibidos por un cliente se guardan en un archivo temporal, a1 que hace referencia la propiedad CacheFile del objeto TSOAPAttachment. Si no se borra este archivo, permanecera en una carpeta que albergue archims temporales. Este codigo produce el mismo efecto visual que una aplicacion cliente que cargue un archivo local en un ClientDataSet, como muestra la figura 23.7. En este cliente SOAP hemos usado un componente HTTPRIO de manera explicita para poder inspeccionar 10s datos entrantes (que posiblemente seran muy grandes y lentos). Por este motivo, hemos puesto a cero una variable nRead global antes de invocar a1 metodo remoto. En el evento OnReceivingData de la propiedad HTTPWebNode del objeto HTTPRIO, aiiadiremos 10s datos recibidos a la variable nRead. Los parametros Read y T o t a l que se pasan a1 evento se refieren a1 bloque de datos especifico que se envia a traves de un socket, por lo que resultan casi inutiles por si solos para inspeccionar el progreso: p r o c e d u r e TForml.HTTPRIO1HTTPWebNodelReceivingData( Read, Total: Integer); begin Inc (nRead, Read) ;


StatusBarl.SimpleText : = IntToStr ( n R e a d ) ; App1ication.ProcessMessages; end ;

90020 Triggerfishy 90030 Snapper 90050 Wrasse 90070 Angelfish 9M80 Cod 90090 Scorpionlish

Clown Triggerf'ish Red Emperor G~anlM m r ~Wrasse Blue Angellish Lunartail Rockead Fnefish

Figura 23.7. El ejemplo Fishclient recibe un ClientDataSet binario dentro de un adjunto de SOAP.

Soporte de UDDI La gran popularidad de XML y SOAP abre nuevas vias para que las aplicaciones de comunicacion B2B interacthen. XML y SOAP proporcionan una base, per0 no bastan (la estandarizacion en 10s formatos XML, en el proceso de comunicacion y en la disponibilidad de la informacion sobre un negocio son todos ellos elementos claves de una solucion real). Entre 10s estandares propuestos para superar esta situacion 10s mas notables son Universal Description, Discovery, and Integration (UDDI, www.uddi.org) y Electronic Business using extensible Markup Language (ebXML, www.ebxml.org). Estas dos soluciones se solapan y difieren en parte y ahora se trabaja con mas empeiio en ellas por parte del consorcio OASIS (Organizationjor the Advancement of Structured Information Standards, www.oasis-open.org). No vamos a entrar en 10s problemas de 10s procesos de negocio; en lugar de eso, solo vamos a comentar 10s elementos tecnicos de UDDI, ya que, de manera especial, Delphi 7 soporta este estandar.

~ Q u es e UDDI? La especificacion Universal Description, Discovery, and Integration (UDDI) es un esfuerzo para crear un catalog0 de servicios Web ofrecidos por empresas de


todo el mundo. El objetivo de esta iniciativa es crear un marco de trabajo abierto, global e independiente de plataformas para permitir que las entidades de negocio se encuentren entre si, definir como interactuan con la red Internet y compartir un registro de negocio global. Por supuesto, la idea es acelerar la adopcion del comercio electronico, en forma de aplicaciones B2B. Basicamente, UDDI es un registro de negocios global. Las empresas pueden registrarse en el sistema, describir su organizacion y 10s servicios Web que ofrecen (en UDDI, el termino "servicios Web" se usa en un sentido muy amplio, incluyendo direcciones de correo electronico y sitios Web). La informacion del registro de UDDI para cada empresa se divide en tres campos:

Paginas blancas: Incluyen informacion de contacto, direcciones y cosas similares. Paginas amarillas: Registran la compaiiia en una o mas taxonomias, incluyendo categorias industriales, productos vendidos por la empresa, information geografica y otras taxonomias (posiblemente personalizables). Paginas verdes: Proporcionan la lista de 10s sewicios Web ofrecidos por la empresa. Cada servicio se lista bajo un tip0 de servicio (llamado un tModel), que puede estar predefinido o un tip0 descrito especificamente por la empresa (por ejemplo en terminos de WSDL). Tecnicamente, el registro UDDI deberia verse como un DNS actual, y deberia tener una naturaleza distribuida similar: varios servidores, reflejados y con almacenamiento de datos para acelerar 10s procesos. Los clientes pueden guardar en cache datos siguiendo unas reglas determinadas. UDDI define modelos de datos especificos para una entidad de negocio, un sewicio de negocio y una plantilla de enlace. El tipo BusinessEntity incluye informacion esencial sobre el negocio, como su nombre, la categoria a la que pertenece informacion de contacto. Soporta las taxonomias de las paginas amarillas, con informacion industrial, tipos de producto y detalles geograficos. El tipo Businessservice incluye descripciones de 10s servicios Web (usados por las paginas verdes). El tip0 principal es solo un contenedor para 10s servicios relacionados. Los s e ~ i c i o pueden s estar ligados a una taxonomia (zona geografica, producto, etc.. .). Toda estructura BusinessService incluye una o mas BindingTemplates (la referencia a1 s e ~ i c i o )La . BindingTemplate tiene un tModel. El tModel incluye informacion sobre formatos, protocolos y seguridad, y referencias a especificaciones tecnicas (probablemente mediante el formato WSDL). Si varias empresas comparten un tModel, un programa puede interactuar con todas ellas con el mismo codigo. Un programa de negocio determinado, por ejemplo, puede ofrecer un tModel para que otros programas interactuen con el, sin importar la empresa que haya adoptado el software. La API de UDDI se basa en SOAP. Mediante SOAP, se pueden registrar datos y consultar un registro. Microsoft tambien ofrece un SDK basado en COM, e


IBM tiene un conjunto de herramientas Java Open Source para UDDI. Las API de UDDi incluyen consultas ( f i n d x x y g e t x x ) y publicaciones ( s a v e x x y d e l e t e x x ) para cada una d e las cuatro estructuras de datos principales ( b u s i n e s s ~ n t i tb~u ,s i n e s s s e r v i c e , b i n d i n g T e r n p l a t e y t M o d e l ) .

UDDI en Delphi 7 Delphi 7 incluye un navegador UDDI que puede usarse para encontrar un servicio Web cuando se importa un archivo WSDL. El navegador UDDI, que muestra la figura 23.8, lo activa el WSDL Import Wizard. Este navegador solo utiliza la version 1 de servidores UDDI (hay disponible una interfaz mas nueva, pero no esta soportada) y tiene unos cuantos registros UDDI predefinidos). Se pueden aiiadir configuraciones predefinidas en el archivo UDDIBrow.ini que se encuentra en la carpeta bin de Delphi. Este es un mod0 muy practico de acceder a informacion sobre servicios Web, pero no es todo lo que permite Delphi. Aunque el UDDI Browser no este disponible como una aplicacion independiente, las unidades de interfaz UDDI estan disponibles (y no es trivial importarlas). Por ello, se puede escribir un navegador UDDI propio.

Figura 23.8. El navegador UDDI incorporado al IDâ&#x201A;Ź de Delphi.

Vamos a bosquejar una solucion sencilla, que es un buen punto de partida para un navegador UDDI mas complete. El ejemplo UddiInquiry, que se muestra en la figura 23.9, tiene un gran numero de caracteristicas, pero no todas ellas funcionara correctamente (en particular las caracteristicas de busqueda por categoria). El


motivo es que usar UDDI implica recorrer estructuras de datos muy complejas, que no siempre se proyectan del mod0 mas obvio mediante el importador WSDL. Esto complica bastante el codigo del ejemplo; por eso solo vamos a mostrar el codigo de una busqueda sencilla, y no todo (otro motivo es que algunos lectores pueden no tener particular interes por UDDI).

Search by Name

Search t o r lmicro

] Description Micro C, Inc. Micro Focus Micro Focus Micro lntormalica LLC MICRO MACHINES Micro Motion Inc. MicroApplications. Inc. rnrcrobizl

We provide systems inte... Welcome to the future of ... Welcome to the future of ... This is a UDDI Business ... Plant and Machinery for ... Micro Motion manufactur ... informalion syslems dev... desc

Search by Category

1 BusinessKay

I

516ab96a-50f5-48b4-9fO4- ... 7e76378cfa28-47a2-b8a... 9566~530-7d59-11d6-8c3... dce959d-200d-4d9e-bee ... ca2551 cc088f-46b7-9cl ...

d4e4b830-fl9e-4edI-9144... a23~901e-834~-4b8~bf3...

87f5ta08-508e-4065-b379 ...

4 _1

A

r ? dv e ~ s ~ o n = " l .encoding=*utf-8" ~l' 1,

-

s o a p : Envelope .<rnlns.io~p="htt~~://sct~emas.xmlsoap.org/soap/envelope/' umlnr-.: ~ ~ 1 = " h t t p : / / w ~ ~ . w 3 . ~ rl/XMLSchema-instance' g/200 .dns: v s d = " h t t p : / / c c ~ v ~ ~ ~ . ~ ~ ~ 3 . o r ~ / 2 0 O 1 / X k 1 L S c h ~ n ~ a ' ~ - <soap:Bodyr - <businessDeta~lgeneric="l.O uperator="Microsoft Corporation" truncated="false" :ri~ln~="urn:urldi-org:clpin: - <bus~nessEnt~ty bus1nesst~ey="59593094-dfld-4f53-9a2c-BbffcBc93513' operator="Microroft Corporation" author~zedName="ScottWitkin"> - .rd~scove~-vURLC>

Figura 23.9. El ejemplo Uddilnquiry presenta un limitado navegador UDDI.

Cuando arranca el programa, enlaza el componente HTTPRIO que alberga con la interfaz Inquiresoap de UDDI, definida en la unidad inquiry-vl que proporciona Delphi 7: procedure TForml.FormCreate(Sender: TObject); begin httpriol-Url : = comboRegistry.Text; inftInquire := httpriol as Inquiresoap; end;

A1 hacer clic sobre el boton Search, el programa llama a la funcion find business de la API de UDDI. Ya que la mayoria de las funciones UDDI-necesitan muchos parhmetros, se ha importado empleando un 6nico parametro basado en un registro de tipo FindBusiness. Devuelve un objeto businessList2:


procedure TForml.btnSearchClick(Sender: var findBusinessData: Findbusiness; businessListData: businessList2; begin httpriol.Ur1 : = cornboRegistry.Text;

TObject);

businessListData : = inftInquire.find-business(findBusindBusinessData); BusinessListToListView (businessListData); findBusinessData.Free; end ;

El objeto bus ines sList 2 es una lista que se procesa en el metodo bus ines sListToListView del formulario principal del programa, mostrando 10s detalles mas importantes en un componente de vista de lista: procedure TForml.businessListToListView(businessList: businessList2); var i: Integer; begin ListViewl.Clear; for i : = 0 to businessList.businessInfos.Len do begin with ListViewl.Items.Add do begin Caption : = businesslist. businessInfos [i] .name; SubItems.Add (businessList.business1nfos [i].description); SubItems.Add (businessList.business1nfos [i].businessKey); end ; end ; end ;

A1 hacer doble clic sobre el elemento de vista de lista, se pueden explorar aun mas sus detalles, aunque el programa muestra la informacion XML resultante en un formato de texto plano (o en una vista XML basada en TWebBrowser) y no lo procesa mas. Como se ha mencionado, no queremos entrar en detalles tecnicos; si se siente interes, se puede analizar con mas detalle el codigo hente.


Parte V

Apendices


Herramientas Delphi del autor

Durante 10s ultimos aiios el autor de este libro ha desarrollado algunos pequeiios componentes y herramientas complementarias de Delphi. Algunas de estas herramientas fueron creadas para libros o como resultado de la ampliacion de ejemplos de libros. Otras fueron escritas como ayuda para tareas repetitivas. Todas estas herramientas estan disponibles gratuitamente y algunas incluyen el codigo fuente. Este aphdice proporciona una lista, incluyendo especialmente las mencionadas en este libro. En el for0 de discusion del autor se ofrece soporte para todas estas herramientas (vease www.marcocantu.com para obtener las direcciones).

CanTools Wizards Este es un conjunto de asistentes que podemos instalar en Delphi, en un menu desplegable extra o como un submenu del menu Tools. Los asistentes (disponibles gratuitamente en www.marcocantu.com/cantoolsw)no estan relacionados entre si y tienen caracteristicas diferentes:

List Template Wizard: Racionaliza el desarrollo de clases similares basadas en listas, cada una con su propio tip0 de objetos. Este asistente se


menciona en el capitulo 4. Como realiza una operacion de busqueda y reemplazo en un archivo fuente base puede usarse siempre que necesitemos codigo repetido y el nombre de la clase (u otra entidad) cambie. O O P Form Wizard: Mencionado en el capitulo 4. Permite ocultar 10s componentes publicados de un formulario, haciendo el formulario m b orientad0 a objetos y ofreciendo un mejor mecanismo de encapsulacion. Debemos ejecutarlo cuando un formulario este activo y rellenara el controlador de eventos O n c r e a t e . Despues, tendremos que mover manualmente parte del codigo a la seccion de inicializacion de la unidad. Object Inspector Font Wizard: Permite cambiar el tip0 de letra del Object lnspector (algo especialmente util para presentaciones, ya que el tipo de letra del Object lnspector es demasiado pequeiio para mostrarse con facilidad en una pantalla de proyeccion). Otra opcion permite modificar una caracteristica interna del Object lnspector y mostrar 10s nombres de 10s tipos de letra (en la lista combinada desplegable de esa propiedad) usando un tipo de letra especifico. Rebuild Wizard: Permite reconstruir todos 10s proyectos de una subcarpeta determinada desputs de cargar cada uno de ellos secuencialmente en el IDE. Podemos usar este asistente para coger una serie de proyectos (como 10s de este libro) y abrir el que nos interesa haciendo clic en la lista:

I

sbookshd7codeU4RunPropR~~1Prop dpr e bmkshd7codeU4\ICompess~Compress

I

Tambien podemos compilar automaticamente un proyecto concreto o comenzar una (lenta) creacion de multiples proyectos: en el cuadro de resultados del compilador, haremos clic sobre un boton para proceder solo si la opcion del entorno correspondiente esta fijada. Si esta opcion del entorno no esta fijada, no veremos 10s errores del compilador, porque 10s mensajes del compilador son reemplazados en cada cornpilacion. Clip History Viewer: Mantiene una lista de elementos de texto que hemos copiado a1 Portapapeles. Un campo de la ventana del visor muestra las ultimas 100 lineas copiadas. Editar ese campo (y hacer clic sobre S a v e ) modifica este historic0 del Portapapeles. Si mantenemos abierto Delphi, el Portapapeles recogera texto de otros programas (pero solo texto, por su-


puesto). En ocasiones ocurren errores relacionados con el Portapapeles provocados por este asistente.

VCL Hierarchy Wizard: Muestra la jerarquia (casi) completa de VCL, incluyendo componentes de terceros que hayamos instalado, y permite buscar una clase y ver multiples detalles (clases basicas y subclases, propiedades publicadas, etc.). Hacer clic en el boton regenera tanto la lista como el arb01 (secuencialmente, por lo que la barra de progreso se muestra dos veces):

La lista de clases se genera usando un nucleo de clases predefinido (faltan algunas por lo que se aceptan sugerencias) y aiiadiendo cada componente de 10s paquetes instalados (10s de Delphi, 10s nuestros y 10s de terceros) junto con las clases de todas las propiedades publicadas que son de tip0 clase. A pesar de todo, las clases usadas solo como propiedades publicadas no se incluyen. Extended Database Forms Wizard: Hace muchas mas cosas que el D a t a b a s e Forms Wizard disponible en el IDE de Delphi, permitiendonos elegir 10s campos que queremos colocar en un formulario y usar conjuntos de datos diferentes de 10s basados en BDE. Multiline Palette Manager: Permite convertir l a c o m p o n e n t Palette de Delphi en un control de fichas con multiples lineas de fichas:


Programa de conversion VclToClx Podemos usar esta herramienta independiente para convertir proyectos de Delphi de VCL a CLX (y viceversa, si lo configuramos para ello). Puede convertir simultaneamente todos 10s archivos de una carpeta y sus subcarpetas. Este es un ejemplo de su resultado:

E !bwks\rnd7code\08\VI1\VI1dpr E-\books\md7code\OB\Pol1Fo1m\PoliForrn dp E:\books\rnd7code\OBF1arnes2\F1arnes2 dp

Do RepbDsI ROld QForms

--

.

-1%

-

-

-_-_I

Fums

El codigo fuente del programa esta disponible en la c a r p e t a T o o l s del codigo del libro. El programa VclToClx convierte nombres de unidades (basandose en un fichero de configuracion) y manipula 10s DFM renombrando 10s archivos DFM a XFM y corrigiendo las referencias en el codigo fuente. El programa no es sofisticado, no analiza el codigo pero busca apariciones del nombre de la unidad seguidos de una coma o un punto y coma, como ocurre en una sentencia u s e s . Tambien requiere que el nombre de la unidad vaya precedido por un espacio, pero podemos modificar el programa para que busque una coma. No debemos saltarnos esta comprobacion, jo la unidad Forms se convertira en QForms per0 la unidad QForms se reconvertira en QQForms!

Object Debugger En tiempo de diseiio, podemos usar el Object Inspector para fijar las propiedades de 10s componentes de nuestros formularios y otros modulos. En Delphi 4,


Borland present6 un Debug Inspector de tiempo de ejecucion, que tiene una interfaz similar y muestra informacion parecida. Antes de que Borland aiiadiera esta caracteristica, el autor implement6 una copia del Object Inspector de tiempo de ejecucion pensado para depurar programas:

DlapMode Enabbd

M d TIW

EutsmkdSckcl

Trw

+Font

[ O W 01343DF8)

Charsc(

Cdn He* Nams Pilch S k

SM

-

1 &ridowTed

,;,.

.

,

l

T m t Nau Roman

IpDeld 0

n

-.

Este programa ofrece acceso de lectura y escritura a todas las propiedades publicadas de un componente, y tiene dos cuadros combinados que permiten seleccionar un formulario y un componente dentro del formulario. Algunos de 10s tipos de propiedad tienen editores de propiedades a medida (listas y similares). Podemos situar el componente Ob je c t D e b b u g e r en el formulario principal de un programa (o podemos crearlo dinamicamente en el codigo): aparecera en su propia ventana. Puede mejorarse, per0 incluso en su forma actual esta herramienta es practica y tienen muchos usuarios. El codigo fuente de este componente esta disponible en la carpeta ~ o o l del s codigo del libro.

Memory Snap Existen multiples herramientas para analizar el estado de la memoria de una aplicacion Delphi. Este es un gestor de memoria personalizado que se conecta con el gestor de memoria por defecto de Delphi, analizando todas las asignaciones y liberaciones de memoria. Ademas de informar del numero total (algo que ahora Delphi hace por defecto), puede guardar una descripcion detallada del estado de la memoria en un archivo. Memory Snap mantiene en memoria una lista de bloques asignados (hasta una cantidad maxima, facilmente modificable), de mod0 que puede volcar


el contenido de la pila a un fichero con una perspectiva de bajo nivel. Esta lista se genera examinando cada bloque de memoria y determinando su naturaleza con tecnicas empiricas que podemos ver en el codigo fuente (aunque no son faciles de comprender). La salida se guarda en un archivo, porque esta es la unica actividad que no requiere una asignacion de memoria que pueda afectar a 10s resultados. Este es un fragment0 de un fichero de ejemplo: 00C035CC: 00C035EO: 00C03730: 00C03744: 00C03968: OOCO3990: 00C039B4: 00C039F4: OOCO3B34: OOCO3B48: 00C03B58:

object: [TList - 161 buffer with heap pointer [00C032BO] string: [5-11 : Edit1 object: [TEdit - 5441 object: [TFont - 361 object: [TSizeConstraints - 321 object: [TBrush - 241 buffer with heap pointer [00C01FE4] buffer with heap pointer [00COlF18] string: [O-01 : dD string: [ll-21: c:\mman.log

El programa puede ampliarse para que analice el uso de la memoria por tipos (cadenas, objetos, otros bloques), vigile 10s bloques no liberados y mantenga las asignaciones de memoria bajo control. El codigo fuente de este componente tambien esta disponible gratuitamente en la carpeta Tools del codigo del libro.

Licencias y contribuciones Como hemos dicho, algunas de estas herramientas estan disponibles con su codigo fuente completo. Estan protegidas bajo licencia LGPL (Lesser General Public License, www.gnu.org/copyleft/lesser.htm),lo que significa que pueden ser usadas gratuitamente y redistribuidas de cualquier modo, incluyendo modificaciones, mientras el autor retiene el copyright. La LGPL no permite cerrar el codigo fuente de nuestras extensiones, per0 podemos usar este codigo de biblioteca en programas comerciales, independientemente de la disponibilidad del codigo fuente. En caso de ampliar estas herramientas corrigiendo errores o aiiadiendo nuevas caracteristicas, el autor solicita que se le envien las actualizaciones de mod0 que pueda distribuirlas y evitar multiplicar el codigo en diferentes versiones, aunque la licencia no nos obliga a ello.


Contenido del CD-ROM

Este libro se basa en ejemplos. Tras la presentacion de cada concept0 o componente Delphi, encontrara un programa de ejemplo (a veces mas de uno) que demuestra como se puede usar dicha caracteristica. En total, en el libro se presentan mas de 300 ejemplos. La mayoria de 10s ejemplos son bastante sencillos y se centran en una unica caracteristica. Los ejemplos mas complejos se elaboran normalmente paso a paso, con pasos intermedios como soluciones parciales y mejoras.

le la base de datos de ejemplo; fonnamt parte da de Delphi. &Enotros casosi es &esa@'la WSLOIJ ~lt:t;jernpw I I I L G ~ ~ S B EMPLOYEE.[ y tambih el sewidor & DWG merflase, pba sqguaeao). -

<

Tambien hay una version HTML del codigo fuente, en la que la sintaxis aparece resaltada, junto con un indice completo de las palabras claves y 10s identificadores (clase, funcion, metodo y nombres de propiedades, entre otros). El archivo del indice es un archivo HTML, por lo que podra utilizar su explorador facilmente para encontrar todos 10s programas que usen la palabra clave o el


identificador Delphi que este buscando (no es un completo motor de busqueda per0 se le acerca mucho). La estructura del directorio del codigo de ejemplo es bastante simple. Basicamente, cada capitulo del libro posee su propia carpeta y una subcarpeta para cada ejemplo (ej: 0 3 \ F i l e L i s t).En el texto, se hace referencia a 10s ejemplos solo por su nombre (ej: FileList).

1

chivo Readrne.de 10s archlvos de c M g q r u , ~ que , , conuene Importanre informacion sobre el uso legal y efectivg sdware.

En la carpeta Delphi7 del CD-ROM encontrara la version de prueba de Delphi 7, la edicion superior limitada en el tiempo. Para poder instalar esta version de Delphi 7, que le permitira seguir sin problemas todos 10s ejemplos descritos en el libro, es necesario que efectue una operacion de registro para obtener el numero de serie y la clave de activacion. No tiene mas que seguir las instrucciones de la utilidad de instalacion para completar el proceso de registro y activar su Delphi 7. Para ello necesitara disponer de una conexion a Internet y una cuenta de correo electronico. En el proceso tendra que crear una cuenta de registro en la pagina Web de Borland, responder a algunas preguntas y, finalmente, obtendra por correo electronico el numero de serie y la clave de activacion. Tambien encontrara en la carpeta PDF, anexos en 10s que le explican entre otras cosas, algunas de las tecnologias que conforman la iniciativa .NET, 10s cambios especificos que se realizaron en el lenguaje Delphi para hacerlo compatible con el Common Language Runtime, etc.


alfabetico

#O, 356#10, 141 #13#10, 141 $, 818 $00000000, 234 SOOFFFFFF, 234 $IFDEF LINUX, 228 $LIBPREFIX, 54 1 $LIBSUFFIX, 541 $LIBVERSION, 541 &, 255, 261, 1083 {$IF), 90 {$IFDEF LINUX), 140 {SIFDEF MSWINDOWS), 140 {$M+), 174, 176, 1133 {$Warn UNSAFE-CAST OFF), 73 {$Warn UNSAFE-CODE OFF), 73 {$Warn UNSAFE-TYPE OFF), 73 -DF, extension, 78 -DP, extension, 79 -PA, extension, 81 .NET, 657, 1051 .NET Framework Assembly Registration Utility, 657 arquitectura, 221, 493 COM y, 656 :=, 51 @, 1117 p d d R e f , 124, 600 declspec(dllexport), 533 -Release, 600, 6 10

aaManua1, 633 abstract, 1 18 Abstractos, metodos, 1 18 Access, 81 5 Acciones, 251, 631 Accion, destino de la, 3 16 Aceleradoras, teclas, 261 Acerca de, 396 ActionManager, 25 1 ActiveForm, 638 ActiveForms, 644 ActiveRecord, 9 14 ActiveX, 598, 633-634, 648, 819, 1037 Control Wizard, 638 controles, 977 Data Objects (ADO), 803 uso de controles, 636 y componentes Delphi, 635 AdapterGrid, 1037 AdapterMode, 1039 Adapterpageproducer, 1042 adCriteriaAIICols, 838 adCriteriaKey, 838 adCriteriaTimeStamp, 838 adCriteriaUpdCols, 838 Add, 194, 276 To Repository, 85 addAffectGroup, 840


Addchild, 276, 1092 AddNode, 280 Addobject, 269 Adjuntos, 1149 AdjustLineBreaks, 144 ADO, 803-805 Cursor Engine, 822 ADO.NET, 845 ADOCommand, 808 ADOConnection, 808, 81 1, 813, 821, 829-830 ADODataSet, 808, 837 ADODB, 812 ADOQuery, 808, 818 ADOStoredProc, 808 ADOTable, 808, 81 1, 816 ADOX, 814 adResyncUnderlyingValues, 840 ADTG, 844 AffectRecords, 840 Afterconnect, 869 AfterDelete, 865 AfterEdit, 687 AfterInsert, 787 Afteropen, 688 AfterPost, 687, 863, 865 Afterscroll, 866 AfterUpdateRecord, 873 Agente de conexion, 87 1 akBottom, 257 akRight, 257, 355 akTop, 355 alBottom, 1060 alclient, 1060 Alignment, 69 1, 862 AllocMemCount, 139 AllocMemSize, 139 AllowAl1Up, 3 17 AllowGrayed, 24 1 AlphaBlendValue, 366 Alto-bajo, tecnica de, 786 alTop, 260, 1060 1083 ampersand (t), Anchor, 285 Anchors, 257, 355 Anclajes, 257 Animate, 366 Animatewindow, 367 ANSI, 81 4 AnsiContainsText, 149 AnsiDequotedStr, 143 AnsiIndexText, 149 AnsiMatchText, 149 AnsiQuoteStr, 143 AnsiReplaceText, 149 AnsiResembleText, 149 AnsiString, 143

Apache, 1057, 1086 API de Windows, 234,265, 531 Aplicaciones Delphi, arquitectura, 403 AppID, 1060 Application, 108, 181, 262 ApplicationEvents, 128, 404 ApplyUpdates, 864,1041, 1075, 1148, 1106 AppServer, 869 Arb01 de datos, 275 Architect Studio, 36 ArrangeIcons, 424 Arrastre, 156 as, 120, 164, 195 ASI400, 807 ASCII, 244 archivo, 975 Asignacion de objetos, 105-107 Asociativas, listas, 198 Assign, 175 Assigned, 109 AssignTo, 175 Associate, 249, 377 11 17 at At, 22 1 Atozed Software, 1050-1051 Attributes, 830 AutoActivate, 633 Autocheck, 250 Autocomplete, 244 AutoCompleteOptions, 245 Autoconnect, 626 AutoHint, 302 AutoHotKey, 261 Automation, 612, 614 Automatization OLE, 6 12 interfaz de, 629 servidor de, 6 17 AutoPaletteScroll, 64 AutoPaletteSelect, 64 Autosnap, 259 AWpHORpNEGATIVE, 367 AW-HOR-POSITIVE, 367 AW-VER-NEGATIVE, 367 AW-VER-POSITIVE, 367 AxBorderStyle, 646

(a),

BabelFish, 1 131 Bands, 3 19 Barra de herramientas, 316 Barras de desplazamiento, 248 BaseCLX, 170, 172, 21 5 BDE, 173, 804 BeforeEdit, 687


BeforePost, 687 BeforeUpdateRecord, 873 BeginThread, 140 BeginTrans, 829 Beveled, 259 Beyond Compare, 46 Biblioteca de clases estandar, 169 de tipos, 6 18 dinamica, 527 en tiempo de ejecucion (RTL), 135 Bibliotecas, 527 del sistema de Windows, 530 BindingTemplates, 11 52 Bit menos significative, 304 bkcancel, 392 bkOK, 392 BLOB, 202, 872-873, 892 campos, 206 Bloqueo optimists, 836 pesimista, 832 recursive, 142 tipos de, 83 1 BMP, 681 extension, 77 body, 1056 Bookmarksize, 902 BoolToStr, 142 Bordes, 232 Borland Memory Manager. 538 Registry Cleanup Utility, 75 BorlndMM.DLL, 154, 538-539 BPG, 69 extension, 77 BPL, extension, 77 BRC32.exe, 75 BRCC32.exe, 75 BringToFront, 412 Businessservice, 1 152 Buttonstyle, 676 Blisqueda, dialog0 de, 798

C#, 45 CAB archivo comprimido, 646 extension, 77 CacheFile, 1 150 Cachesize, 825 Cadenas de conexion, editor de, 809, 8 16 exportar, 537

caFree, 4 10 Caja negra, 95 Calculado, campo, 695, 790 Callback. 563 Callbacks, 22 1 Campos, 687 del formulario. elimination, 185 Canal Alpha, 366 CancelBatch, 835 Cancelupdate, 83 5 Caption, 133, 164, 261 CaretPos, 304 Cascade, 424 Cascading Style Sheets (CSS), 993 CASE, 567 Casilla de ver~ficacion,24 1 Casos de uso, 572 Catalogo, 8 14 CD-ROM, 1 165 CDATA, 1088 cdecl, 53 1 cdPreventFullOpen, 394 CDS, 669 CellData, 993 CFG archivo, 70 extension, 77 Changed, 5 10 Characters, 1099 CheckBox, 241 Checked, 245 CheckListBox, 244,272 ChildValues, I089 ciMultiInstance, 874 ckAttachToInterface, 627 ckNewlnstance, 627 ckRemote, 627 ckRunningInstance, 627 ckRunningOrNew, 627 clActiveCaption, 234 clActiveForeground, 234 Clases de escucha, 189 Class Completion, 49-50, 92 ClassPDColorPropPage, 642 Class-DFontPropPage, 642 Cla~s~DPicturePropPage, 642 Class-DStringPropPage, 642 Classes, 169, 180, 21 5 CLassInfo, 165 Classparent, 164 ClassType, 164 Clave de registro, 842 Clave externa, 790 clBase, 233-234 clBtnFace, 234 clCream. 233


clDisabledBase, 234 clGreen, 233 ClientDataSet, 173, 670, 696, 727, 822, 854, 871, 1085, 1105 Clientelservidor, 848-849 Clientelservidor, arquitectura, 728 ClientToScreen, 252,263 Clip History Viewer, 1160 clMedGray, 233 clMoneyGreen, 233 clNone, 1071 Clone, 828 cloneNode, 1091 clRed, 233 clsilver, 233 clSkyBlue, 233 clUseClient, 822, 834, 840 clUseCursor, 825 clUseServer, 822 clWhite, 233 clwindow, 234 CLX, 38, 170, 172, 220, 222 cm-Activate, 500 cm-BiDiModeChanged, 500 cm-BorderChanged, 500 cm-Changed, 643 cm-ColorChanged, 500 cm-Ctl3DChanged, 500 cm-CursorChanged, 500 cm-Deactivate, 500 cm-EnabledChanged, 500 cm-Enter, 500 cm-Exit, 500 cm-FocusChanged, 500 cm-FontChanged, 500 cm_GotFocus, 500 cm-LostFocus, 500 cm-MouseEnter, 498 cm-MouseExit, 498 cmdFile, 844 CmdGotoPage, 1037 CmdLine, 140 CmdNextPage, 1037 CmdPrevPage, 1037 coBookMark, 829 Code, 177, 189 Completion, 50-5 1, 102 Explorer, 46-48 Insight. 49 Parameters, 52 Templates, 52 Codificaciones, 1082 Colecciones, 196 Color, 233 Key, 366 ColorBox, 245

ColorDialog, 394 Colores, 233 ColorRef, 537 ColorToString, 269 ColumnLayout, 243 Columns, 243, 676, 992 COM+, 154, 599, 648-650, 845, 851, 874, 1148 eventos, 653 COM aplicacion contenedor, 630 objetos locales, 1148 Comandos, 250 ComboBox, 243 ComboBoxEx, 245 ComConst, 154 ComCtrls, 276 CommandText, 817, 844 CommandType, 844 Commit, 78 1 CommitRetaining, 782 CommitTrans, 829 ComObj, 154 Comparevalue, 146 Compartido, controlador de eventos, 30 1 Compatibilidad, 1 13 Compatible en tipo, 124 Compilador advertencias de, 73 mensajes de, 73 Compilar, 7 1-72 Complejos, ntimeros, 152 Component Palette, 38, 63, 66, 171 Componentcount, 18 1 ComponentIndex, 180 Components, 181-182 Components, matriz, 181 Componentstate, 41 1 Compuestos, documentos, 629 ComServ, 154 Concurrencia, 65 1 Conexion agente de, 871 cadenas de, 809-81 1 Conjuntos de registros desconectados, 840-841 permancntes, 843-844 Connected, 858 Connection, 8 1 1, 872 ConnectionBroker, 855, 871 Connectionstring, 809, 81 1-8 12, 8 17, 822 ConnectKind, 627 Conscientes de 10s datos, controles, 878 ConstraintErrorMessage, 861 Constraints, 258, 260, 861 Constructor virtual, 133 Constructores, 103-104


Consulta en vivo, 779 libre, 800 Container, 630 Contenedor, 242 Contenedores, 193, 196 ContentType, 995 Contnrs, 196 Contribuciones, 1 164 Controlador, 61 3 ControlBar, 31 8, 320 Controls, 182, 393 Convert, 76, 155 CONVERT.EXE, 208 ConvUtils, 148, 154 Cookies, 1056 CoolBar, 3 18-3 19CORBA, 852 Correo electronico, 973 protocolos de, 974 enviado, 975 recibido, 975 Cracker, 112 Create, 103 CreateComObject, 623 createElement, 1090 CreateFileMapping, 548 CreateForm, 380 CreateGUID, 143 CreateHandle, 235 Createoleobject, 623 Createparams, 235 CreateTable, 91 8 CreateWindowEx, 354 CreateWindowHandle, 235 Creational Wizard, 595 Cross-platform Form Modules (XFM). 224 csDestroying, 4 1 1 csDropDown, 243 csDropDownList, 243 csExecute, 978 CSS, 993 archivo, 995 cssimple, 243 ctAnchor, 1039 ctDynamic, 825 ctstatic, 825 Cuadricula, 676 Cuadricula, HTML, 1061 Cubos, 198 CUR, extension, 77 Currency, 862 Cursores, 822 conjunto de claves, 824 dinamico, 824 estatico, 824 solo avance. 824

tipo de, 823 ubicacion de, 822 CursorLocation, 834 CursorRect, 264 CurValue, 839 CustomConstraint, 86 1

Data Link, 81 1 Data, 177 Data-aware, 237, 675, 877-879, 882, 897, 91 1 controles orientados a campos, 880 Database Explorer, 75 Datachange, 885 DataCLX, 170, 173 DataEvent, 879 DataField, 675, 878, 881 DataGrid, 1 1 13 DataLinkDir, 812 DataNavigador, 1 1 13 DataRelation, 845 DataSet, 845, 861, 991 DataSetAdapter, 1037 DataSetField, 871 DataSetPageProducer, 987 Datasetprovider, 862 DataSetReader, 845 DataSetTableProducer, 987, 991 Datasnap, 850 Datasource, 675, 818, 878, 881, 1077 DateUtils, 99, 136, 148, 217 DAX, 613 DBCheckBox, 675 DBCtrlGrid, 882, 888 DBEdit, 675 dbExpress, 727-728, 804 dbGo, 173, 807, 828 DBGrid, 112-1 13, 675-676, 788, 793. 818. 859, 871, 888, 917, 1142 personalization, 893-897 DBI, extension, 82 DBNavigator, 675 DCI, extension, 82 DCOM, 652, 85 1, 869 DCOMConnection, 854, 858 DCP, extension, 77 DCT, extension, 82 DCU archivo, 552 extension, 7 8 DDE, 598 DDL, 789 DDP, extension, 78 Debugger Optiones, 129


Decision Cube. 173 Defittributes, 239 default, 100 DefaultColWidth, 890 DefaultDrawing, 892 DefaultExpression, 861 DefaultRowHeight, 896 DefaultTextLIneBreakStyle, 140 DefaultTimeout, 1043 DefineProperties, 175 Definepropertypage, 644 Definidores de estado, 250 Delete, 194, 1037 delete-xx, 1 153 Delphi ediciones de, 36 paquetes, 550 Delphi for .NET Preview, 656 Delphi Form Module (DFM), 224 DELPHI32.DCT, 6 6 DelphiMM, 154 Delta, 674 paquete, 865 DEM, extension, 82 Dephi ActiveX (DAX), 173 deprecate, 90 Depurador, 87 Derechos de acceso, 1047 DESC, 826-827 Descendiente, conversion, 1 19 Design Critic, 584 DesignIntf, 522 DesignOnly, 562 DesingEditors, 522 Destroy. 104, 108 DestroyComponents, 180 Destruccion de objetos, 108 Destructores, 104 DevelopMentor, 1 130 DFM, 174, 207, 224 archivos, 57, 83 extension, 7 8 DFN, extension, 79 Diagram Editor, 574 Diagram View, 54 Diagram, 54-55Diagramas de actividad, 574 de clase, 569 de colaboracion, 574 de componentes, 574 de dependencia de unidades, 574 de despliegue, 574 de estado, 574 de mapa mental, 574 de robustez, 574 de secuencia. 57 1

Difference, pestaiia, 583 Dinamica, biblioteca, 72 Dinamicas, propiedades, 8 12-8 13 Dinamico, enlace, 528 Dinamicos, mttodos, 1 17 Directorio en un conjunto de datos, 9 18-9 19 DisableCommit, 652 DisableIfNoHandler, 3 14 Disparador de servidor, 788 Disparadores, 79 1 dispinterface, 614, 61 6-617, 640 DisplayFormat, 862, 864, 992 DisplayLabel, 689, 862 DisplayName, 891 DisplayText, 891 DisplayType, 1039 DisplayValues, 862 Displaywidth, 818, 862 Distributed COM (DCOM), 8 5 1 Divisas, 158 DivMod, 146 DLL, 528-530, 531-532, 535, 544, 549-551, 648 en tiempo de ejecucion, 542 creacion de, 534-535 de C++, 532-534 extension, 79 normas de creacion, 530 dmAutomatic, 157, 277 DMT, extension, 82 DNS, 1 152 doAutoIndent, 1090 Document Import Signature, 586 Document Type Definition, 1098 Documentacion, 585 DOF archivo, 83 extension, 79 DOM, 1085 para creacion de documentos, 1090-1094 programacion con, 1085-1 086 DOMVendor, 1083 DoVerb, 631 DPK, extension, 79 DPKL, extension, 79 DPKW, extension, 79 DPR, 83 extension, 79, 581 DragMode, 157, 277 DragTree, 278 Draw, 431 Drawcell, 890 DrawText, 892 DRO, extension, 82 DropDownRows, 676 dsBrowse, 686 dsCalcFields, 686


dsCurValue, 686 dsEdit, 686 dsFilter, 686 dshactive, 686 dsInsert, 686 DSM, extension, 80 dsNewValue, 686 dsOIdValue, 686 DST archivos, 39 extension, 82 dt-Singleline, 892 dt-WordBreak, 892 DTD, 1098 Dual, soporte, 222 DXSock, 986 dynamic, 117

ebXML, 1 15 1 EchoMode, 238 Edit, 522, 1037, 1039 componente, 237-238 EditFormat, 862 EditMask, 239, 862 Editor Properties, 5 1 EDivByZero, 127 EFileStreamError, 206 ElnvalidCast, 120 Emptyparam, 81 3 Enablecommit, 652 Enabled, 232, 3 14 Encapsulacion. reglas de la, 186 Encapsulado, 95, 10 1 con propiedades, 97 campos protegidos y, 1 11 EndDocument, 1099, 1101 EndElement, 1099-1 100 EndThread, 140 EndUserSessionAdapter, 1045 Enlace de datos, 878 archivos de, 81 1-812 dinamico, 528 posterior, 114 Enlazador inteligente, 186 Ensamblaje, 656 EnsureRange, 145 Enterprise Studio, 36 Entrada de teclado, 356 EnumModules, 139, 562-563 Environment Options, 40-4 1, 53 Environment Variables, pagina, 40 Envoltorio, 200-20 1

EqualsValue, 146 Esquematica, informacion. 8 13 Estilo, 304 Estilos de ventana, 354 Estatica, sobrescritura, 2000 Euro, 158 Event Types View, 584 Evento, 191 Eventos, 188, 190 COM+, 653 programacion guiada por, 4 12 Events, 236 Excel, 815, 817, 821 Excepciones, 124-125 clases de, 127 depuracion y, 128 soporte, 124 except, 125-126 Exception, 127 EXE, 553 extension, 8 0 EXEC, 1056 Execute, 507 ExecuteTarget, 5 12 Executeverb, 522-523 ExpandFileName, 144 Experts, 87 exports, 53 1 , 537 Extended Database Forms Wizard, 1161 Extendedselect, 243 extern "C", 533 External Translation Manager, 75 external, 539 ExtractStr~ngs,2 16

FalseBoolStrs, 142 fdApplyButton, 394 FetchDetails, 873 FetchDetailsOnDemand. 873 FetchOnDemand, 873 Fetchparams, 868 fgConflictingRecords, 839 fgPendingRecords, 835 FieldAddress, I76 FieldByName, 687-890 FieldDataLink. 884 Fields, 1037 FieldsDef, 902 FieldValues, 688 FIFO, 197 Filecreate, 144 Filter, 673, 814 Filtered, 8 14


FilterGroup, 835-836, 839 Filtrado, 672 finally, 125-126 Find, 395 find-xx, 1153 Findclose, 162 Findcomponent, 184, 392 FindFist, 162 FindGlobalComponent, 185 FindNext, 162 FindNextPage, 288 FList, 201 FloatToCurr. 143 FloatToDateTime, 143 Flotantes, sugerencias. 262 fmOpenRead, 205 fmShareDenyWrite, 205 fmShareExclusive, 206 Foco, 188, 256 de entrada, 254 FocusControl, 54, 255 FollowChange, 674 FOnChange. 19 1 FOnCLick, I90 Font, 233, 896 FontDialog, 394 FontNamePropertyDisplayFontNames,60 Footer, 992 ForEach, 199 Form Designer, 56, 62, 83 Formstyle, 423 Formularios dentro de paquetes, 553 formview, 1039 FRecordCount, 91 0 Free, 104, 108-109, 198 FreeAndNil, 108 FreeLibrary, 542, 556 FreeMem, 139 FreeNotification, 41 1 FriendlyName, 1062 FROM, clausula, 821 FromCommon, 161 FromStart, 1071 fsMDIChild, 423 fsMDIForm, 423 FTP, 982 Fuentes, 233 desplegables, 60 FullExpand, 277 function, 92

Generacion de codigos, 175 Generador, 787

Generadores de 64 bits, 786 GeneratorField, 788 Gestion de archivos, 162 get-xx, 1 153 GetBufStart, 276 GetCanModify, 921 Getclass, 556 GetDataAsXml. 1 107 GetFileVersion, 144 GetInterfaceExternalName, 1 137 GetKeyState, 304 GetLocaleFormatSettings, 144 GetLongHint, 303 GetMem, 139 GetMemoryManager, 139 GetMethExternalName, 1 137 GetNamePath, 175 Getobjectcontext, 652 GetOleFont, 627 Getolestrings, 628 Getowner, 175 GetPackageDescription, 562 GetPackageInfo, 562, 564 GetPackageInfoTable, 139, 562 GetProcAddress, 542, 546 GetPropInfo, 178 GetPropValue, 178 GetShareData, 549 GetShortHint, 303 GetStrProp, 178 GetSystemMetrics, 355 GetTickCount, 624 GetVerb, 522 GetVerbCount, 522 GExperts, 4 1 glibc, 154 Global Desktop, 39 Google, 978 Goteo de memoria, 105 GreaterThanValue, 146 grEOF, 9 12 GroupBox, 181 , 2 4 1 Grouped, 3 17 GroupIndex, 3 17, 429, 6 3 1 GUID de Windows, 786 GUlD, 122, 657, 813, 1135 GuidAttribute, 657

Handle, 235 HandlesPackages, 560 HandlesTarget, 5 12 Hash, 198 Header, 992


Hebras, 1141 Herencia, 109, 1 13, 200, 432 de un formulario base, 433 Hide, 232 HideOptions, 1047 Hilos, 4 12 HInstance, 563 Hint, 303 Hintcolor, 262 HintHidePause, 262 Hintpause, 262 Hintshortpause, 262 Hojas de estilo, 993 HorzScrollBar, 249 HTML, 987 extension, 80 HTML 4 , 9 8 8 archivo, 82 1 estructuras, 1068 generation de paginas, 988 HTTP, 852,977, 982 servidor, 9 8 5 socket, 869 HttpDecode, 969 HttpEncode, 969 HTTPRIO, 1 134, 1 137, 1 144 HTTPSoapDispatcher, 1135 HTTPSoapPascalInvoker, 1135 httpsrvr.dll, 852, 855 HTTP We bNode. 1 1SO

IAppServer, 850-851, 1 146-1 148 IAppServerSOAP, 1 146-1 148 IBBackupService, 777 IBConfigService, 777 IBDatabase, 777 IBDatabaseInfo, 777 IBDataSet, 780, 792 IBEvents, 777 IBInstall, 777 IBLogService, 777 IBQuery, 780 IBRestoreService, 777 IBSecurityService, 777 IBServerProperties, 777 IBSQL, 777 IBSQLMonitor, 777, 783 IBStatisticalService, 777 IBUninstall, 777 IBUpdateSQL, 779-780 IBValidationService, 777 IBX, 777 ICO, extension, 77

IConnectionPoint, 653 lconview, 246 IconView, 27 1 IDAPI, 803 Identificador de sesion, 1056 Identificador global, 790 IdHTTPServer, 985, 994 IDispatch, 139, 614-6 15, 623 IDL, lenguaje, 6 18 IdMessage, 975 IDOMAttr, 1086 IDOMElement, 1086 IDOMNode, 1086 IDOMNodeList, 1086 IDOMParseError, 1083 IDOMText, 1086 IdPop3,975 IdSMTP, 975 IdURL, 984 IFDEF, 227 Iflhen, 145, 149 ignorablewarning, 1100 IInterface, 122-124, 139 IInvokable, 139, 1132, 1145 IISAM, 815-816, 818 ImageIndex, 252 ImageList, 59 Images, 252 Import Type Library, 622 ImportedConstraint, 86 1 IN, clausula, 821 IncludeInDelta, 874 IncludeTrailingBackslash, 144 IncludingTrailingPathDelimiter,144 Increase, 9 7 Indexado, 671 IndexFieldNames, 826 IndexOf, 149 IndexOfName, 194 InetXCustom, 1 1 10-1 11 1 InetXPageProducer, 1108-1 109, 11 13 Infinity, 145 InflateRect, 892 Information de clase, 167 inherited, 48, 116, 434, 893 InheritsFrom, 1 19, 164 INI, archivos, 39 Inicial, pantalla, 397 InputBox, 396 InputQuery, 396 Inquiresoap, 1 154InRange, 145 Insert, 194, 1039 Insertcomponent, 180, 182 InsertObjectDialog, 6 3 1 Installable Indexed Sequential Access Method (IISAM), 81 5


InstanceS~ze,164 Instancias de formulario, 793 Integrated Translation Environment (ITE), 530 InterBase Admin, 777 InterBase Express, 173, 783-785 InterceptGUID, 854 Interfaces, 12 1 Interfaz de envio. 616 de usuario, 283 InterLockedIncrement, 1066 Intermediacion, 6 13 InternalInitFieldDefs, 904 Internet Express, 1 108 Internet, pagina, 40 InternetCloseHandle, 983 Internetopen, 982-983 InternetOpenURL, 982-983 InternetReadFile, 982-983 INTO, clausula, 82 1 IntraWeb, 173, 1049-1050 arquitecturas, 1057 Introduced, 4 8 IntToStr, 141 Invalidate, 365-366 InvalidateRegion, 366 IObjectContext, 652 is, 119, 164 ISAPI, bibliotecas, 1057 IsConsole, 139 IsEqualGUID, 143 IsInfinite, 145 IslnTransaction, 652 IWClientSideDataSet, 1076 IWClientSideDataSetDBLink, 1076 IWCSLabel, 1076 IWCSNavigator, 1076 IWDataModulePool, 105 1 IWDBGrid, 1071, 1075-1076 IWDialogs, 105 1 IWDynamicChart, 1076 IWDynGrid, 1076 IWEdit, 1052 IWGranPrimo, 105 1 IWGrid, 1061 IWImagen, 1063 IWLayoutMgrForm, 1069 IWLayoutMgrHTML, 1069 IWListBox, 1052 IWModuleController, 1067 IWOpenSource, 1051 IWPageProducer, 1066 IWRun, 1052 IWServerControllerBaseNewSession,1065 IWTemplateProcessorHTML, 1069 IWTranslator. 105 1

IWURL, 1061 IXMLDocument, 1087 IXMLDocumentAccess, 1083 IXMLNode, 1087 IXMLNodeCollection, 1087 IXMLNodeList, 1087

LabeledEdit, 59, 238 LabelPosition, 238 Labelspacing, 238 Languaje Exceptions, 129 LargeImages, 270 LayoutChanged, 895 IbOwnerDrawFixed, 267 IbOwnerDrawVariable, 267 Left, 232 LessThanValue, 146 library, 90, 535 LIC, extension, 80, 640 Licencias, 1164 LIFO, 197 Ligeros, clientes, 850 like, 788 Linestart, 2 16 Linuy 162, 170, 200, 227, 932 List Template Wizard, 1159 Lista de referencias grafica, 270-275 Listas, 242 ListBox, 242, 267 listener, 189 Listview, 270 LIVE-SERVER-AT-DESIGNTIME, 626 LoadBalanced, 874 LoadFromFile, 193, 276, 844 LoadFromStream, 204 LoadLibrary, 542, 556 Loadpackage, 556 LocalConnection, 855 Locate, 673 Locked, 6 3 1 LockType, 831, 834, 840 LogChanges, 675 LoginFormAdapter, 1046 LongMonthNames, 110 IoPartialKey, 673 Lote, 839 Lotes, 834 Lotus 1-2-3, 815 ItBatchOptimistic, 831, 834, 837, 840 Itoptimistic, 83 1 ItPessimistic, 83 1-832 ItReadOnly, 83 1 ItUnspecified, 831


maAutomatic, 26 1 Macros, 587 Madre, clase, 163 Maestroldetalle, 792, 870, 104 1 MainForm, 380 MainMenu, 59 MakeObjectInstance, 431 Maletin, 844 malloc, 154 maManual, 261 Manejadores de mensajes, 1 17 MapViewOfFile, 549 Marshalling, 599, 61 3 MaskEdit, componente, 238 MasterAdapter. 1041 Math, 145, 217 Matrices, propiedades basadas en, 100 MaxRecords, 1 109 MaxSessions, 1043 MaxValue, 862 Mayusculas, 789 mbMenuBarBreak, 252 mbRight, 253 MDAC, 805, 807, 842, 850 MDI, 423 aplicaciones, 422 cliente, 423 Memo, componente, 239 Memory Snap, 1 163-1 164 Mensajes de correo, generacion automatica de, 974 Menu Des~gner,2 5 1 Menu, 261 MergeChangesLog, 675 message, 1 17 MessageBox, 396, 536 MessageDlg, 395 MessageDlgPos, 395 Messages, 230 Method Implementation Code Editor, 582 MethodAddress. 176 MethodName, 176 MidasLib, 669Middleware, 812 MilliSecondOfMonth, 148 MIME, tipo, 995 MinSize, 259 MinValue, 862 MmSystem, 497 ModalResult, 390, 505 ModelMaker, 41, 47, 567-569, 592 ModelMaker, integracion de Delphi con, 576-575 Modelo de hilos, 649 de referencia a objetos, 104 transaccional, 649

Modified, 643 ModifyAccess, 1047 ModuleIsPackage, 56 1 Move, 921 Mozilla, 1055 MPB, extension, 570 mrOk, 505 mscorlib.dll, 658 MSXML SDK, l o 8 6 MTS, 648, 851-852 Multicapa, aplicaciones Datasnap. 847 Multiline Palette Manager, 1161 Multiplataforma, 932 MultiSelect, 242, 280 MultiSelectStyle, 280 MyBase, 173, 1085 Metodo, punteros a, 189 Modelo de codigo, 578 Modulo de datos transaccionales, 65 1

Name mangling, 537, 552 Name, propiedad, 184 Namevalueseparator, 194 NegInfinity, 145 nerError, 152 nerLoose, 152 nerstrict, 152 NetCLX, 170, 173 New Items, cuadro de dialogo, 85 Newvalue, 839 nil, 104, 108-109, 187 No modal, cuadro de dialogo, 390 NodeIndentStr, 1090 NodeType, 1088 Nodevalue, 1088Nod0, 1088 de arbol, 280 Nombre-valor, pares, 194 Nombres de proyecto, cambio de, 540 Normalizacion, 795 Norrnalizacion, reglas de, 790-791 Notebook, 285 Notification, 4 1 1 null, 152 NullAsStringValue, 152 NullEqual~tyRule,152 NullMagnitudeRule, 152 NullStrictConvert. 152

OASIS, 1 15 1 OBJ, extension, 80


ObjAuto, 2 17 Object Browser, 74 Debugger, 1162-1 163 Inspector Font Wizard, 1160 Inspector, 58. 59, 191 Pascal, 89 Repository, 84-85, 535 Treeview, 54, 59, 61-62 ObjectBinaryToText, 208 ObjectPropertiesDialog, 632 Objects, 268 Ob.jeto COM, 599 interno, 633 Objetos de automatization, alcance de, 624 y memoria, 107 OCX, 636, 646 extension, 80 ODBC, 803, 805 of object, 189 ofAllowMultiSelect, 394 ofExtensionDifferent, 394 Office, aplicaciones, 629 OID global, 786 OID, 786 OiFontPk, 60 OLAP. 173 Oldcreateorder, 225 OldValue, 839 OLE Automation, 97, 612 OLE DB, 803, 805, 809 proveedores, 805 OLE, 598, 629 Olecontainer, 630 Olevariant, 637-638 OnActionUpdate, 404 OnActivate, 404 OnCalcFields, 696 OnCanViewPage, 1047 Onchange, 191, 236, 924 OnChar, 493 Onclick, 166, 184, 190-192, 236 Onclose, 783 OnCloseUp, 244 OnColumnClick, 273 OnCommandGet, 994 Oncompare, 273 OnContextMenu, 253-254 Oncreate, 379 OnCreateNodesClass, 280 OnDataChange, 879 OnDestroy, 557 OnDoubleClick, 39 1 OnDragDrop, 157, 277

OnDragOver, 157, 277 OnDrawItem, 265-266 OnDropDown, 244 OnEnter, 255-256 OnException. 128 OnExit, 255 OnFilterRecord, 686. 8 14 OnFormatCeII, 993 OnGetData, 875 OnGetDataSetProperties. 874 OnGetEditMask, 246 OnGetPickList, 247 OnGetValue, 1044 OnHelp, 353, 404 OnHint, 303, 404 OnIdle, 404 OnKeyDown, 236 OnKeyPress, 237, 356 OnMeasureItem, 265-266, 268 OnMessage, 404 OnMouseDown, 133,236,274 OnMouseMove, 236 OnPageAccessdenied, 1047 OnPaint, 236, 365 OnReceivingdata, 1 150 OnReconcileError, 853, 863, 865 OnRecordChangeComplete, 839 OnRecordsetCreate, 8 13 OnShorCut, 404 Options, 862, 872 Oracle, 807, 842 ORDER BY, clausula, 827 Orientado a objetos, enfoque, 135 out, 101 overload, 93 override, 1 14, 123 Owner, 182, 231 Owner-draw, 265

Page, 1057 Pagecontrol, 181, 284, 1083 PageControls, 285 PagedAdapter, 1037 Pageproducer, 987 PageScroller, 249 Pagesize, 1037 PakageInfo, 139 Panel, 181 Paquetes, 527, 553 interfaces en, 558 versiones de, 55 1 de datos, 853 delta, 853, 865


Paradox, 8 16, 82 1 param, 647 Paramcount. 140 Params, 868, 1 109 ParamStr, 137, 140 Parcheo, 546 Parent, 133, 23 1 Parentcolor, 233, 635 ParentFont, 233, 635 Parser, 1083 Parametros consulta preparada con, 790 consultas por, 868 PAS. extension, 80 Pascal orientado a objetos, 89 PasteSpecialDialog, 632 Patrones de diseiio, 590-592 PChar, 537-538 Permanencia, 207 Permisos, 1043 Personalizada clase stream, 210 variante, 153 Pes~mista,bloqueo, 832 Peticion de entrada, 1045 PickList, 676 PixelsPerInch, 378-379 Plantilla, 66 Plantillas de componentes. 65 de codigo, 45, 593 modificar, 52 platform, 90 Playsound, 497 poAllowCommandText, 873 poCascadeDeletes, 873 poDefault, 368 poDefaultPosOnly, 368 Preferences, pagina, 4 0 Principal, formulario, 429 Privada, parte de la declaration, 186 Privado, 96 private. 96, 176 procedure, 92 ProcessMessages, 365, 4 12 Professional Studio, 36 Propiedades ficha de, 642 por su nombre, 177 Propietario cambio de, 182 componente, 180 protected, 96, 176 Protegido, 96 Proveedores, 806 ProviderFlags, 862

ProviderName, 859 Proyecto en blanco, plantilla de. 85 opciones de, 69 Proyectos, gestionar, 67-68 public, 96, 100, 176 published, 100, 174, 176 Paginas amarillas, 1 152 blancas, 1 152 verdes, 1 152 P~iblico,96

QDialogs, 224 QForms, 224 QGraphics, 224 QPainterH, 222 QStdCtrls, 223 Qt, 2 2 0 , 2 2 2 Qt/C++, 236 QtFreeEdition, 222 Query, 1039 QueryInterface, 600, 610 QueryTableProducer, 988

RadioButton, 24 1 RadioGroup. 241 raise, 125 Random, 145 RandomFrom, 145 RandomRange, 145 Rangos, 248 Rave Reports, 932 RAVE, 173 Rave, 93 1 RC, extension, 81 RDBMS, 849 RDS, 805 RDSConnection, 808 Read, 202, 1 150 read, 98 ReadBuffer, 203 Readcomponent, 203 ReadComponentRes, 208 Rebuild Wizard, 1 1GO ReconcileProducer, 1 1 12 Recordcount, 826 Recordsize, 9 12 Redondeo, 147 Referencias de clase, 130-132


Refresco, 865 Refresh, 865 RefreshRecords, 865-866 RefreshSQL, 796 REG, extension, 81, 609 regasm, 657 Register, 524, 53 1 Registerclass, 186, 558 RegisterClasses, 186 RegisterConversionType, 16 1 RegisterPooled, 874 Registro de Windows, 78 1, 1 148 Registro, 64 Registros, conjunto de, 81 3 Reglas de negocio, 926 Reingenieria, 587-588 Release, 124, 1063 Remoteserver, 859 Remove, 194 RemoveComponent, 182 Repaint, 365 Replace, 395 Replicacion, 827-829 Required, 1062 Reserva, de conexiones, 84 1 Resizestyle, 260 Resolutor, 864 Resolver, 864 ResolveToDataSet, 863-864 Resource Explorer, 76 Resource Workshop, 76 resourcestring, 14 1 Restricciones, 860 Resync, 840 ResyncValues, 840 Rethink Orthogonal, 595 RethinkHotKeys, 261 RethinkLines, 26 1 Retrollamada, 563 Retrollamadas, 221 Reutilizacion, 1 15 RGB, 234 RichEdit, 239 Rollback, 781 RollbackRetaining, 782 RollbackTrans, 829 Root, 924 ROT, 627 RoundTo, 146 Row, 243 RowAttributes, 992 RowCurrentColor. 107 1 RowLayout, 243 RowLimit, 107 1 RPS, extension, 8 1 rsline, 260

rsPattern, 260 rsupdate, 560 RTL, 123, 127, 152, 170, 220 unidades de la, 136 VCL y, 138 RTTI, 119, 121, 133, 164, 556, 558, 1093 operadores, 12 1 RunOnly, 562 RWebApplication, 1065

safecall, 869 SafeLoadLibrary, 542, 556 Samevalue, 146 save-xx, 1 15 3 SaveDialog, 394 SavePictureDialog, 394 Savepoint, 674 SaveToFile, 193, 843 SaveToStream, 204 SAX, 1085, 1099 ScaleBy, 377 SCHEMA.IN1, 8 19-820 ScktSrvr.exe, 852 Screen, 378-379, 409 Screensnap, 368 ScrollBar, 248 ScrollBox, 250 SecondOtWeek, 148 Seguimiento activo, 274 Seguridad de tipos, 199 SelAttributes, 239 SelCount, 243 Selected, 243 Selections, 280 SelectNextPage, 287-288 Self, 93-94, 104, 123, 189 Sender, 157 ServerGUID, 854 ServerName, 854 Servicios de componentes de Microsoft, consola de, 654 Sesiones, 1043 gestion de, 1064-1066 Sessionservice, 1045 SetAbort, 652 SetBounds, 232 SetComplete, 652 SetDataField, 882 SetFieldData, 92 1 SetForegroundWindow, 421 SetMemoryManager, 139 SetOleFont, 627 Setolestrings, 628


SetShareData, 549 SGML, 1080 SharedConnection, 855 ShareMem, 154 ShellApi, 977 ShellExecute, 977 ShortMonthNames, 110 Show, 559 ShowColumnHeaders, 273 Showing, 233 ShowMessage, 178, 396, 536 ShowMessagePos, 396 ShowModal, 545, 559, 1063 Showsender, 166 Signals, 236 Signatures, 207 SimpleObjectBroker, 873 SimplePanel, 255, 301 SimpleRoundTo, 147 SimpleText, 30 1 Sincronizacion, problemas, 3 16 Sincronizador multilectura, 142 Singleton, patron, 590 siProviderSpecific, 813 Size, 202, 8 18 SizeGrip, 302 SizeOf, 164 Skin, 304 SmallImages, 270 SMTP, 975 SNA Server, 807 Snap To Grid, 56 SOAP Server Application, 1 134 SOAP, 652, 852, 1129, 1131, 1140, 1145 DataSnap sobre, 1 145 depuracion de cabeceras, 1143 proyeccion sobre Pascal, 1 133 SOAPConnection, 1148 SOAPMidas, 1 147 SOAPServerIID, 1147 Sobrecargadas, funciones, 537 Sobrescribir, 1 15 Socket, conexion de, 970 Socketconnection, 854 SortType, 273 Soundex, 149 Source Options, 45, 52 SpeedButton, 2 3 1 Splitter, 258, 260 SQL Monitor, 75 SQL Server Profiler, 834 SQL Server, 834 SQL edicion, 801 insert, 796 sentencia, 728, 779, 800, 792

servidor, 672 servidores, 648, 727 update, 796 SQLDataSet, 857, 1140 SQLQueryTableProducer, 988 Standalone, 1057 StartDocument, 1099, 1101 StartElement, 1099-1 100, 1102-1 103 starting with, 788 State, 245, 914 State-setters, 250 StateImages, 270 Stateless COM (MTS), 85 1 Statics, 4 8 StatusBar, 102 stBoth, 273 stData, 273 stdcall, 531, 534, 1145 StdConvs, 148, 155, 159, 161 StdCtrls, 223 Stones, 148 stored, 100 StoreDefs, 670 StrCopy, 92 1 Stream, 2 14 Streaming, 174-175, 202, 206 sistema de, 186 Streams, 205, 209 compresion de, 2 13 conjunto de datos basado en, 9 17-9 19 StringReplace, 67 1 StringToColor, 269 StringToFloat, 495 StripParamQuotes, 988 StrUtils, 149, 2 17 style, 1055 SubMenuImages, 252 Sugerencias, personalization, 263 SupportCallbacks, 854 SupportedBrowsers, 1055 Supports, 561, 829 Sustitucion de etiquetas, 1045 Synchronize, 97 1 SyncObjs, 217 SysConst, 141 SysInit, 139-140, 561 System, 139-140 SysUtils, 136, 141-142, 162,217

TabbedNotebook, 285 Tabcontrol, 284 Tabla de metodos virtuales (VMT), 123, 552 TableAttributes, 992


TableName, 816, 819, 9 18 Taborder, 254 Tabs, 64 TabSet, 285 Tabsheet, 284, 286 TabSheets, 285 TabStop, 254 TabVisible, 2 8 8 TActiveForm, 644 TADTField, 692 Tag, 188, 990 TAggregateField, 692 TAlignment, 3 17 TApplication. 130, 404 TArrayField, 692 TAutoIncField, 692 TBCDField, 693 tbHorizonta1, 424 TBitBtn, 499 TBitmap, 204 TBits, 216 TBlobFieId. 204 TBlobStream, 205 TBooleanField, 693 TBucketList, 198 TButton, 164, 166, 221 tbvertical, 424 TCompressStream, 2 13 TControl, 171. 219, 230, 283 TControlClass, 133 TConvTypeFactor, 16 1 TCPIIP, sockets, 852 TCurrencyField, 693 TCustomControl, 639 TCustomDBGrid, 895 TCustomEdit, 494 TCustomGrid, 889, 892 TCustomListBox, 244 TCustomMemoryStream, 204 TCustomVariantType, 15 1, 153 TDataEvent, 879 TDataLink, 878-879 TDataModule, 216, 855 TDataSet, 675, 897, 898, 902, 914, 919 TDatasetField, 870 TDataSetProvider, 863 TDateField, 690 TDateTime, 97, 139, 148, 916, 923 TDateTimeField, 693 TDBMS. 823 TDecompressionStream, 2 15 TDecompressStream, 2 13 TDump, 534 Teamsource, 75 Tema, 304 Template, 66

Terminate, 1063 Text IISAM, 819-820 Text, 133, 244 TextBrowser, 983-984 TextHeight, 268 Texto, archivos de, 8 19 Textviewer, 240 TField, 992 TFieldDataLink, 879, 886, 888 TFields, 902 TFileData, 92 1, 924 TFileRec, 14 1 TFileStream, 204 TFloatField, 690 TFMTBCDField, 693 TForm, 645 TFormatSettings, 144 TFormClass, 132 tgcustom, 990 tglmage, 990 tgLink. 990 TGraphicControl, 230-231, 639 TGraphicField, 693 tgTable, 990 THandleStream, 204 THashedStringList, 199 THeapStatus, 139 THintInfo, 264 Threads, 142, 412 threadvar, 1065 THTTPRIO, 1 133 TIcon, 204 TidThreadSafeInteger, 1066 Tiempo de ejecucion verificaciones en, 200 Tile, 424 TileMode, 424 TMenuItem, 261 TMethod, 177, 189 tModel, 1 152 TMREWSync, 142 TMultiReadExclusiveWriteSynchronizer,142 TNotifyEvent, 2 15 TO-DOList, 41 TObject, 121, 139, 163-164, 165-167 TObjectBucketList, 198 TObjectList, 197, 919 TObjectQuery, 197 Tocommon, 16 1 TODO comentarios, 42 extension, 81 TOleContainer, 63 1 TOleControl, 640 TOleServer, 626 TOleStream. 205


ToolBar, 30 1, 3 13 Tooltip Expression Evaluation, 53 Tooltip Symbol Insight, 4 8 Top, 232 Total, 1 150 TOwnedCollection, 2 15 Transaccion. 65 1. 783 componente de, 78 1 context0 de, 782 Transaction DDL, propiedad, 829 TransformNode, 11 19 Transparentcolor, 366 TransparentColorValue, 366 TReader, 206-207 TRecall, 2 16 TReconcileErrorForm, 840 TRect, 15 1 Treeview, 270, 275-276, 1087 Tres capas, arquitectura logica de, 849 TResourceStream, 2 0 5 , 2 1 0 Trolltech, 220 TrueBoolStrs, 142 try, 125-126 trylexcept, 129 TryEncodeData, 143 TryEncodeTime, 143 TryStrToCurr, 143 TryStrToDate, 143 TryStrToFloat, 143 TSchemaInfo, 8 13 TSearchRec, 92 1 TSmallPoint, 15 1 TSOAPAttachment, 1050 TSoapAttachment, 1149 TSoapDataModule. 1 147 TSQLTimeStampField, 694 TStack. 197 TStream, 202-203 TStringList. 193, 204, 215, 217, 508 TStrings, 149, 193, 204, 2 15 TStringStream, 204 TTextRec, 141 TThreadList, 2 15 TTimeField, 694 TTimeStamp, 923 TToolButton, 250 TTreeItems, 280 TTreeNode, 278 Tuberia (I), 303 Turbo Grep, 76 Pascal, 4 6 TextFile, 130 Register Server, 76 TUserSession, 1065, 1066, 1070 TVarBytesField, 694

TVarData, 139 TVariantField, 695 TVariantManager, 139 TWebAppPageModuleFactory, 1046 Type Library Editor, 1135 Type Library Importer, 657 TypeInfo. 179 Types, 15 1.230 TypInfo, 178, 217, 1093

UDDI Browser, 1 153 UDDI, I151 en Delphi 7. 1153 UDL, extension, 8 1 UML, 567, 568-569, 572 UndoLastChange, 674 Union SQL, 833 Unique Table, propiedad, 834 Unit Code Editor, 580 Union interna, 796, 798 UnloadPackage, 556-557 Update, 365 UpdateBatch, 835 UpdateCriteria, 838 UpdateList, 202 UpdateOIeObject, 643 UpdatePropertyPage, 643 UpdateRegistry, 857, 874 UpdatesPending, 836 UpdateSql, 860 Updatestatus, 835 UpdateTarget, 5 12 UpDown, 249 upper, 788 UserFrame, 1071 uses, sentencias, 226, 229 UseSOAPAdapter, 1 148 Usuarios, 1043

Validacion, 1098 ValueFromIndex, 194 ValueListEditor, 246 Values, 194 var, 101, 107, 118 VarArrayCreate, 673 VarArrayOf, 673 VarCmply 153 VarComplexCreate, 153 Variant, 136 Variantes personalizadas, 152


Variants: 151, 153, 217 Varias paginas, aplicaciones de, 1060 VarUtils, 15 1, 21 7 VBX, 634 VCL Hierarchy Wizard, 1161 VCL, 38, 138, 170,202. 215 VclToClx, 1 I62 Ventanas hijo. 423 marco, 423 Verbos, 6 3 1 Verificacion, valor de, 198 VertScrolIBar, 249 ViewAccess, 1047 virtuaI, 1 14, 1 17 Virtuales metodos, 1 17 teclas, 3 13 Virtuals, 4 8 Visible, 233. 862 VisibleButtons, 676 VisibleRowCount, 896 Vista de diferencias, 582 virtual, 894 Vistas, 54 Visual Basic, 633 VisualCLX, 170, 172, 221 VCL frente a. 220 Visuales, controles, 2 19 Visualizador de registros, 889

W3C, 987, 1082, 113 1 Watch List, 87 WAV, archivos, 496 Web App Debugger, 75, 1068, 1143-1 144, 1146 Web Surface Designer, 1040, 1042 Web aplicaciones, 105 1 exponer como un servicio, 1144 servicio, 1 130, 1 134 WebApplication, 1060 WebBroker, 978, 1045, 1066, 1145 Webconnection, 855 WebDispatch, 1109 WebSnap, 48, 978, 1041-1042, 1066 WebUserList. 1045 while, 167 WideCompareStr, 143 WideCompareText, 143 WideFormat, 143 WideLowerCase, 143 WideSameStr. 143

WideSameText, 143 Widestring, 650 WideUpperCase, 143 Widgets, 220 Width, 259 WINAPI, 53 3 WindowMenu, 423 WindowPRoc, 235 Windows 2000, 353 Windows, 230, 536, 932 Windowstate, 368 WinInet, 982 Winsight, 75 WndProc, 235 Word, 629 Wordstar. 46 Worl Wide Web Consortium (W3C), 987 wpLoginRequired, 1046 Write, 202

xaAbortRetaining, 830-83 1 xaCommitRetaining, 830-83 1 XDB, extension, 1098 Xerces, 1086 XFM, 174,224 XHTML, 988 XLSReadWrite, 8 17 XMethods, 1 13 1 XML Mapper, 75, 1103-1 104, 1141-1 145 XML Schema Validator (XSV), 1099 XML Schema, 1099 XML, 669, 671, 844, 1079-1080 analizador sintactico, 843 bien formado, 1082 con transformaciones, 1 103 datos, 1139 esquemas, 1098 interfaces de enlace de datos, 1094-1099 paso de documentos, 1140 sintaxis, 1080-1082 XMLBroker, 1108-1 109, 11 15 XMLData, 669 XMLDocument, 1083 xmldom, 1086 XMLTransform, 855, 1 105 XMLTransformClient, 855, 1 105 XMLTransformProvider, 855, 1105, 1 142


La biblia de Delphi 7 03