12 octubre 2007

Trabajando con documentos XML (y III)

Aunque es posible trabajar con un documento XML usando solamente el componente XMLDocument y la interfaz IXMLNode es mucho más fácil utilizar el asistente XML Data Binding.

Dicho asistente toma los datos del esquema de un archivo XML a través de su archivo asociado DTD y realiza una nueva unidad con una interfaz que maneja nuestro documento XML sin tener que navegar por los nodos.

CREANDO UNA INTERFAZ PARA CLIENTES.XML

Para crear una unidad nueva para nuestro documento clientes.xml hay que hacer lo siguiente:

1. Seleccionamos en Delphi la opción File -> New -> Other...

2. Estando situados en la pestaña New seleccionamos el icono XML Data Binding y pulsamos OK.


3. En la ventana siguiente seleccionamos el archivo cli.dtd y pulsamos Next.


4. Después nos aparece otra ventana donde se nos permite modificar el nombre de la clase a crear así como los nombres que van tener las propiedades y métodos de la nueva clase. Lo dejamos como está y pulsamos Next.



5. La última pantalla del asistente nos muestra como va a ser definitivamente la interfaz que va a crear. Sólo nos queda pulsar Finish.



Va a generar la siguiente unidad:

{**************************************************************************}
{ }
{ XML Data Binding }
{ }
{ Generated on: 11/10/2007 11:15:57 }
{ Generated from: D:\Desarrollo\DelphiAlLimite\XML\cli.dtd }
{ Settings stored in: D:\Desarrollo\DelphiAlLimite\XML\cli.xdb }
{ }
{**************************************************************************}

unit UInterfazClientes;

interface

uses xmldom, XMLDoc, XMLIntf;

type

{ Forward Decls }

IXMLClientesType = interface;
IXMLClienteType = interface;

{ IXMLClientesType }

IXMLClientesType = interface(IXMLNodeCollection)
['{A21F5A80-EBA7-48F5-BFE1-EB9F390DFBBD}']
{ Property Accessors }
function Get_Cliente(Index: Integer): IXMLClienteType;
{ Methods & Properties }
function Add: IXMLClienteType;
function Insert(const Index: Integer): IXMLClienteType;
property Cliente[Index: Integer]: IXMLClienteType read Get_Cliente; default;
end;

{ IXMLClienteType }

IXMLClienteType = interface(IXMLNode)
['{624B6C2E-4E94-4CE0-A8D4-FE719AA7D975}']
{ Property Accessors }
function Get_Nombre: WideString;
function Get_Nif: WideString;
function Get_Saldopte: WideString;
function Get_Diaspago: WideString;
procedure Set_Nombre(Value: WideString);
procedure Set_Nif(Value: WideString);
procedure Set_Saldopte(Value: WideString);
procedure Set_Diaspago(Value: WideString);
{ Methods & Properties }
property Nombre: WideString read Get_Nombre write Set_Nombre;
property Nif: WideString read Get_Nif write Set_Nif;
property Saldopte: WideString read Get_Saldopte write Set_Saldopte;
property Diaspago: WideString read Get_Diaspago write Set_Diaspago;
end;

{ Forward Decls }

TXMLClientesType = class;
TXMLClienteType = class;

{ TXMLClientesType }

TXMLClientesType = class(TXMLNodeCollection, IXMLClientesType)
protected
{ IXMLClientesType }
function Get_Cliente(Index: Integer): IXMLClienteType;
function Add: IXMLClienteType;
function Insert(const Index: Integer): IXMLClienteType;
public
procedure AfterConstruction; override;
end;

{ TXMLClienteType }

TXMLClienteType = class(TXMLNode, IXMLClienteType)
protected
{ IXMLClienteType }
function Get_Nombre: WideString;
function Get_Nif: WideString;
function Get_Saldopte: WideString;
function Get_Diaspago: WideString;
procedure Set_Nombre(Value: WideString);
procedure Set_Nif(Value: WideString);
procedure Set_Saldopte(Value: WideString);
procedure Set_Diaspago(Value: WideString);
end;

{ Global Functions }

function GetClientes(Doc: IXMLDocument): IXMLClientesType;
function LoadClientes(const FileName: WideString): IXMLClientesType;
function NewClientes: IXMLClientesType;

const
TargetNamespace = '';

implementation

{ Global Functions }

function GetClientes(Doc: IXMLDocument): IXMLClientesType;
begin
Result := Doc.GetDocBinding('Clientes', TXMLClientesType, TargetNamespace) as IXMLClientesType;
end;

function LoadClientes(const FileName: WideString): IXMLClientesType;
begin
Result := LoadXMLDocument(FileName).GetDocBinding('Clientes', TXMLClientesType, TargetNamespace) as IXMLClientesType;
end;

function NewClientes: IXMLClientesType;
begin
Result := NewXMLDocument.GetDocBinding('Clientes', TXMLClientesType, TargetNamespace) as IXMLClientesType;
end;

{ TXMLClientesType }

procedure TXMLClientesType.AfterConstruction;
begin
RegisterChildNode('Cliente', TXMLClienteType);
ItemTag := 'Cliente';
ItemInterface := IXMLClienteType;
inherited;
end;

function TXMLClientesType.Get_Cliente(Index: Integer): IXMLClienteType;
begin
Result := List[Index] as IXMLClienteType;
end;

function TXMLClientesType.Add: IXMLClienteType;
begin
Result := AddItem(-1) as IXMLClienteType;
end;

function TXMLClientesType.Insert(const Index: Integer): IXMLClienteType;
begin
Result := AddItem(Index) as IXMLClienteType;
end;

{ TXMLClienteType }

function TXMLClienteType.Get_Nombre: WideString;
begin
Result := ChildNodes['nombre'].Text;
end;

procedure TXMLClienteType.Set_Nombre(Value: WideString);
begin
ChildNodes['nombre'].NodeValue := Value;
end;

function TXMLClienteType.Get_Nif: WideString;
begin
Result := ChildNodes['nif'].Text;
end;

procedure TXMLClienteType.Set_Nif(Value: WideString);
begin
ChildNodes['nif'].NodeValue := Value;
end;

function TXMLClienteType.Get_Saldopte: WideString;
begin
Result := ChildNodes['saldopte'].Text;
end;

procedure TXMLClienteType.Set_Saldopte(Value: WideString);
begin
ChildNodes['saldopte'].NodeValue := Value;
end;

function TXMLClienteType.Get_Diaspago: WideString;
begin
Result := ChildNodes['diaspago'].Text;
end;

procedure TXMLClienteType.Set_Diaspago(Value: WideString);
begin
ChildNodes['diaspago'].NodeValue := Value;
end;

end.

UTILIZANDO LA NUEVA INTERFAZ PARA CREAR DOCUMENTOS XML

La unidad creada por el asistente la he guardado con el nombre UInterfazClientes.pas lo que significa que hay que introducirla en la sección uses del formulario donde vayamos a utilizarla.

Vamos a ver como damos de alta un cliente utilizando dicha interfaz:

var
Clientes: IXMLClientesType;
begin
XML.FileName := ExtractFilePath( Application.ExeName ) + 'clientes.xml';
XML.Active := True;
Clientes := GetClientes( XML );

with Clientes.Add do
begin
Attributes['id'] := '3';
Nombre := 'FERNANDO RUIZ BERNAL';
Nif := '58965478T';
Saldopte := '150.66';
Diaspago := '45';
end;

XML.SaveToFile( ExtractFilePath( Application.ExeName ) + 'clientes5.xml' );
end;

He cargado el documento XML como siempre y utilizando la interfaz IXMLClientesType cargo la lista de clientes del documento clientes.xml. Después doy de alta un cliente y grabo el documento como clientes5.xml.

Una cosa cosa interesante que se puede hacer es activar la propiedad doAutoSave que está dentro de la propiedad Options del componente XMLDocument lo que implica que el documento se guardará automáticamente conforme vayamos modificando los elementos del documento XML.

La ventaja de convertir nuestro documento XML en una interfaz persistente es que ahora podemos manejar la lista de clientes como si fuera una base de datos utilizando los métodos get y set de cada uno de los campos así como las rutinas para alta, baja o modificación de elementos dentro del documento. Sólo es cuestión de echarle una mirada a la nueva unidad creada para ver las posibiliades de la misma.

Pruebas realizadas en Delphi 7.

9 comentarios:

Javier dijo...

La verdad es que el XML Data Binding te soluciona mucho la papeleta a la hora de trabajar con los XML.

A mi gusto al Binding le faltaría una función adicional para cargar el XML de un String...

function LoadClientesXML(const XMLData: WideString): IXMLClientesType;
begin
Result := LoadXMLData(XMLData).GetDocBinding('Clientes', TXMLClientesType, TargetNamespace) as IXMLClientesType;
end;

Delphi al Límite dijo...

Tienes razón pero también puedes utilizar la propiedad XML del componente XMLDocument para cargar el documento XML como texto.

Un cordial saludo.

Oscar dijo...

Hola. Me ha sido mucho tu explicación pero tengo un problema que no puedo resolver. Tengo una unidad creada por medio del Delphi XML Data Binding que implementa demasiadas interfaces. Estoy creando una aplicación de demo para crear un xml de ejemplo y voy creando bien los nodos en la estructura que corresponde pero cuando llega a uno de los nodos hijos que implementa una interface IXMLNodeCollection sale este error "EIntfCastError... "Interface not supported"" cuando creo uno de los nodos hijos. Te puedo mandar alguna parte de la aplicación para que me digas en dónde está el error? gracias. Oscar.

Administrador dijo...

Eso tiene que deberse a que has intentado meter algún tipo de variable directamente al nodo sin pasarla a string antes.

Recuerda que los nodos son de texto. Tienes que encargarte de hacer bien las conversiones.

No me envíes el proyecto porque casi no tengo ni tiempo de mantener este blog.

Saludos.

Oscar dijo...

Hola gracias por la respuesta pero sigo sin poder solucionarlo ya que la interfaz que implementa IXMLNodeCollection

IXMLDocumentosDigitales = interface(IXMLNodeCollection)

no contiene nodos IXMLNode sino una interfaz distinta (que implementa IXMLNode).

IXMLDocumentoDigital = interface(IXMLNode)

Como nunca creé archivos XML con data binding no se si se crea primero el objeto hijo aparte y después se lo agrega o se utiliza directamente el método ADD del padre para agregar el nodo.

function Add: IXMLDocumentoDigital;

En este punto es donde sale el error ya que no estoy cumpliendo (ni se cómo) cumplir con los requisitos de la interfaz del hijo.

Espero tu respuesta si te das cuenta rápido, sino gracias igualmente por tu atención.

Administrador dijo...

La verdad es que si no veo el código fuente al completo me pierdo.

Anónimo dijo...

Saludos...en esta parte no entiendo como se puede hacer para determinar cuantos registros por asi decirlo tiene el archivo XML y como se hace para recorrer uno por uno.

Muchas gracias...

Administrador dijo...

Puedes hacer un Count de los nodos para ver cuantos elementos tiene.

Anusky dijo...

Tengo una duda, una vez que tengo creada la unidad del Binding, uso la interfaz para introducir datos, estoy intentando crear un XML nuevo partiendo desde 0, sólo a través de la interface.
Código:
**En mi interfaz, la etiqueta principal es esta:
IXMLDocumentType = interface(IXMLNode)
**

Luego en el data module, tengo un componente TXMLDocument con nombre XML y un botón desde donde llamo a crear el fichero:

procedure TFPantalla.tbGeneraXMLClick(Sender: TObject);
var
fichero: IXMLDocumentType;
begin
fichero := NewDocument;

(**meto datos usando la interface que me ha creado el data binding**)

Ahora que ya tengo todos los datos, ¿como los meto en un archivo XML nuevo? He intentado: XML.ChildNodes.Add(fichero);
XML.SaveToFile('archPrueba.xml');
pero me da un acceso de violacion. ¿Como meto la interface en un nuevo archivo XML? Muchas gracias, es la primera vez que uso esto y estoy totalmente perdida. Un saludo!

end;

Publicidad