Umas das grandes discussões entre defensores das linguagens de código gerenciado (como C# e Java) e defensores das linguagens puras, isto é, não gerenciadas (como C, C++ e Delphi para Win32) é justamente a diferença mais marcante entre os dois conjuntos de linguagens : o gerenciamento da memória usada pelas variáveis e objetos nos programas.
Nas linguagens gerenciadas, o programador não precisa se preocupar em comandar a liberação da memória usada por objetos que ele tenha criado num programa pois o Garbage Collector faz isso de forma automática quando o objeto sai de escopo, ou seja, quando ele não está mais sendo utilizado. A vantagem é que sempre toda memória alocada pelo programa será devolvida ao sistema operacional quando ele terminar. A contrapartida é que haverá uma thread monitorando continuamente a execução do programa (com possível impacto em performance) além do fato de não se ter controle sobre o momento em que o destrutor do objeto será de fato chamado.
Por outro lado, nas linguagens não gerenciadas é responsabilidade do programador desalocar qualquer memória que ele tenha alocado em seu programa. Como esse é um procedimento manual, eventualmente pode escapar alguma memória sem ser devolvida, o que, em situações extremas, pode resultar até na queda do sistema operacional. Mas, este método produz programas mais rápidos e o programador tem total controle sobre o instante em que cada destrutor é invocado.
À parte a discussão sobre qual das abordagens é a melhor, os desenvolvedores C++ Builder e Delphi Win32 têm uma ferramenta para ajudá-los a depurar seus programas e encontrar eventuais falhas no gerenciamento manual de memória. O FastMM é uma biblioteca que substitui o BorlndMM.DLL, que é o gerenciador padrão dos programas feitos nessas duas linguagens. O MM no nome de ambas as bibliotecas é abreviação para Memory Manager, ou Gerenciador de Memória.
Diferente do Code Guard, o FastMM detecta apenas os memory leaks, apresentando os resultados da verificação num relatório gravado ao final da execução do seu programa. Projetos feitas em ambas as linguagens extraem melhores benefícios do FastMM se estiverem compilados para depuração. Com essa opção ligada, o gerenciador consegue apontar com precisão o local onde foi alocada uma variável que não tenha sido removida da memória.
Em C++, o processo de substituição do gerenciador é mais simples pois implica em simplesmente trocar a biblioteca BorlndMM.DLL original por aquela distribuída junto com o FastMM (ou que tenha sido compilada por você mesmo com as configurações personalizadas). A única ressalva a ser feita é que há uma opção de linkagem nos projetos C++ Builder que permitem embutir no binário final o conteúdo do gerenciador de memória, eliminando a necessidade da biblioteca externa. Por isso, substituir a biblioteca só vai funcionar se o projeto estiver compilado para usar o gerenciador dinamicamente. Na versão que eu tenho do C++ Builder, esta opção aparece na página Linker das opções de projeto e tem o nome de Use Dynamic RTL.
Em projetos do Delphi para Win32, as funcionalidades providas em BorlndMM.DLL são sempre linkadas juntas, produzindo um executável ou biblioteca sem essa dependência externa, resultando num processo de substituição um pouco mais elaborado. Neste ambiente, você terá que incluir em seu projeto o fonte do FastMM e garantir que esta unit seja a primeira a aparecer na cláusula Uses do fonte do seu projeto.
Normalmente, não é necessário fazer quaisquer alterações na configuração do FastMM para depurar um projeto. No entanto, se alguma situação exigir, as configurações dele podem ser encontradas no fonte FastMM4Options.Inc, onde há uma lista de opções ricamente comentadas (em inglês) que podem ser ligadas ou desligadas de acordo com suas necessidades. As opções estão espalhadas em seções organizadas pelo tipo de efeito abordado, como por exemplo as vinculadas à melhoria do tratamento do gerenciador em projetos multi threaded (concorrência), as que controlam a forma com que os memory leaks serão reportados, as que compatibilizam o gerenciador com opções específicas do projeto e as relacionadas à depuração do seu programa quando o gerenciador está presente. Apenas lembro que o FastMM4Options.Inc é parte dos fontes do FastMM e que as alterações introduzidas nele exigem que se recompile o projeto para que surtam efetivamente os efeitos desejados.
O FastMM é um projeto open source hospedado no Source Forge, a partir de onde você pode fazer o download tanto das bibliotecas prontas para diversas versões do Delphi e do C++ Builder quanto os códigos fonte para que se possa compilar sua própria versão com um conjunto específico de opções ligadas.
22 de dezembro de 2010
Detectando memory leaks em seus programas Delphi/C++ Builder
Postado por
Luís Gustavo Fabbro
às
19:14
Marcadores:
C++ Builder
,
Delphi
,
Ferramentas
0
comentários
17 de dezembro de 2010
Design Patterns em Delphi - Mediator - parte II
Dada a natureza do problema abordado pelo Design Pattern comportamental Mediator, normalmente a implementação de uma solução com ele resulta numa hierarquia de classes bastante extensa. Basta lembrar que o conceito é aplicável ao conjunto de telas (Forms) de um sistema, onde cada nova tela herdada é um mediador e os componentes inseridos nela podem conversar entre si de maneiras específicas, sem que as classes de componentes necessitem ter conhecimneto umas das outras. O conceito do Mediator foi introduzido num post em novembro, acessível neste link. Apenas para relembrar o básico: o Mediator propõe centralizar em uma única classe base a responsabilidade pela comunicação de todas as outras envolvidas na solução de um problema.
Isto posto, pretendo mostrar aqui a implementação de um exemplo simples, proposto naquele post inicial. É o exemplo da torre de controle de tráfego aéreo, onde a comunicação entre os participantes pode ser implementada de uma forma mais genérica, fazendo com que seja necessária apenas uma herança do mediador. Segue uma reprodução do diagrama UML com o exemplo proposto:
Começo a codificação pela interface que estabelece as regras de mediação, isto é, o próprio Mediator. Ela é normalmente assentada como uma classe abstrata a partir da qual todas as possibilidades de mediação são herdadas.
No exemplo, há apenas uma herança mas o mais comum é ter que trabalhar com diversas delas, como no caso de se usar telas como mediadores para gerenciar a comunicação entre os elementos de interface visual presentes (os componentes da tela). A classe TWFila é apenas auxiliar, sendo usada aqui para controlar a fila de aeronaves que solicitarem algum procedimento à torre. Num projeto real, é praticamente regra que tenhamos neste ponto instâncias de classes específicas cuja comunicação será mediada, implementando regras de negócio únicas. Por causa disso, cada herança do mediador é normalmente aplicada a um único cenário predeterminado.
A declaração dos Colleagues (objetos cuja comunicação é mediada) também é bem direta:
O único ponto importante a ressaltar é a existência do membro _Torre, que é uma instância da classe mediadora. Isso reforça o conceito de que a comunicação entre os diversos tipos de Colleagues deve ser levada a cabo exclusivamente através do mediador, isto é, nenhum Colleague deve ter conhecimento da existência dos demais. Como estas classes são fracamente acopladas, podemos introduzir novos Colleagues sem prejuízo para as heranças já estabelecidas na solução.
O trecho abaixo mostra um exemplo da comunicação usando o mediador.
Este é um exemplo bastante simples, com as instâncias das classes do tipo Colleague inseridas numa fila. De novo : no mundo real, teríamos instâncias específicas dessas classes enviando mensagens umas às outras. Por fim, as classes representando aeronaves são muito parecidas entre si e, por isso, inclui neste post a declaração de apenas uma delas, omitindo as demais.
Isto posto, pretendo mostrar aqui a implementação de um exemplo simples, proposto naquele post inicial. É o exemplo da torre de controle de tráfego aéreo, onde a comunicação entre os participantes pode ser implementada de uma forma mais genérica, fazendo com que seja necessária apenas uma herança do mediador. Segue uma reprodução do diagrama UML com o exemplo proposto:
Começo a codificação pela interface que estabelece as regras de mediação, isto é, o próprio Mediator. Ela é normalmente assentada como uma classe abstrata a partir da qual todas as possibilidades de mediação são herdadas.
TWFila = class
public
procedure push (obj: TObject);
function pop : TObject;
end;
TWAeronaveAbstrata = class;
TWTorreAbstrata = class
public
procedure concedePermissao(Aeron: TWAeronaveAbstrata); virtual;abstract;
procedure solicitaPouso (Aeron: TWAeronaveAbstrata); virtual;abstract;
procedure solicitaDecolagem (Aeron: TWAeronaveAbstrata); virtual;abstract;
end;
TWTorreAeroportoCGN = class(TWTorreAbstrata)
protected
_FilaPista : TWFila;
public
procedure concedePermissao(Aeron: TWAeronaveAbstrata); override;
procedure solicitaPouso (Aeron: TWAeronaveAbstrata); override;
procedure solicitaDecolagem (Aeron: TWAeronaveAbstrata); override;
end;
public
procedure push (obj: TObject);
function pop : TObject;
end;
TWAeronaveAbstrata = class;
TWTorreAbstrata = class
public
procedure concedePermissao(Aeron: TWAeronaveAbstrata); virtual;abstract;
procedure solicitaPouso (Aeron: TWAeronaveAbstrata); virtual;abstract;
procedure solicitaDecolagem (Aeron: TWAeronaveAbstrata); virtual;abstract;
end;
TWTorreAeroportoCGN = class(TWTorreAbstrata)
protected
_FilaPista : TWFila;
public
procedure concedePermissao(Aeron: TWAeronaveAbstrata); override;
procedure solicitaPouso (Aeron: TWAeronaveAbstrata); override;
procedure solicitaDecolagem (Aeron: TWAeronaveAbstrata); override;
end;
No exemplo, há apenas uma herança mas o mais comum é ter que trabalhar com diversas delas, como no caso de se usar telas como mediadores para gerenciar a comunicação entre os elementos de interface visual presentes (os componentes da tela). A classe TWFila é apenas auxiliar, sendo usada aqui para controlar a fila de aeronaves que solicitarem algum procedimento à torre. Num projeto real, é praticamente regra que tenhamos neste ponto instâncias de classes específicas cuja comunicação será mediada, implementando regras de negócio únicas. Por causa disso, cada herança do mediador é normalmente aplicada a um único cenário predeterminado.
A declaração dos Colleagues (objetos cuja comunicação é mediada) também é bem direta:
TWAeronaveAbstrata = class
protected
_Aguardando: boolean;
_Torre: TWTorreAbstrata;
public
constructor Create (ATorre: TWTorreAbstrata);
function aguardarPermissao : boolean; virtual;abstract;
procedure pousar; virtual;abstract;
procedure decolar; virtual;abstract;
procedure liberarPista; virtual;abstract;
end;
TWJatoExecutivo = class(TWAeronaveAbstrata)
public
constructor Create (ATorre: TWTorreAbstrata);
function aguardarPermissao : boolean; override;
procedure pousar; override;
procedure decolar; override;
procedure liberarPista; override;
end;
protected
_Aguardando: boolean;
_Torre: TWTorreAbstrata;
public
constructor Create (ATorre: TWTorreAbstrata);
function aguardarPermissao : boolean; virtual;abstract;
procedure pousar; virtual;abstract;
procedure decolar; virtual;abstract;
procedure liberarPista; virtual;abstract;
end;
TWJatoExecutivo = class(TWAeronaveAbstrata)
public
constructor Create (ATorre: TWTorreAbstrata);
function aguardarPermissao : boolean; override;
procedure pousar; override;
procedure decolar; override;
procedure liberarPista; override;
end;
O único ponto importante a ressaltar é a existência do membro _Torre, que é uma instância da classe mediadora. Isso reforça o conceito de que a comunicação entre os diversos tipos de Colleagues deve ser levada a cabo exclusivamente através do mediador, isto é, nenhum Colleague deve ter conhecimento da existência dos demais. Como estas classes são fracamente acopladas, podemos introduzir novos Colleagues sem prejuízo para as heranças já estabelecidas na solução.
O trecho abaixo mostra um exemplo da comunicação usando o mediador.
procedure TWJatoExecutivo.pousar;
begin
{ Solicita autorização à torre e aguarda permissão }
_Aguardando := true;
_Torre.solicitarPouso ();
While (aguardarPermissao() ) do
Sleep (1000);
{ Com a permissão concedida, a pista é liberada para o procedimento da aeronave seguinte }
liberarPista;
end;
begin
{ Solicita autorização à torre e aguarda permissão }
_Aguardando := true;
_Torre.solicitarPouso ();
While (aguardarPermissao() ) do
Sleep (1000);
{ Com a permissão concedida, a pista é liberada para o procedimento da aeronave seguinte }
liberarPista;
end;
Este é um exemplo bastante simples, com as instâncias das classes do tipo Colleague inseridas numa fila. De novo : no mundo real, teríamos instâncias específicas dessas classes enviando mensagens umas às outras. Por fim, as classes representando aeronaves são muito parecidas entre si e, por isso, inclui neste post a declaração de apenas uma delas, omitindo as demais.
15 de dezembro de 2010
Criando Esquema para validar um XML - parte III
A sintaxe para criação de esquemas XSD é bem mais flexível e poderosa do que os exemplos que mostrei no último post sobre esse assunto. Para tornar mais realista o exemplo da carga de produtos mostrado lá, podemos melhorá-lo agregando novas regras.
O defeito mais patente naquele exemplo é o fato de que apenas um produto por vez pode ser incluído no XML. Numa carga, faz mais sentido aceitar que se informem diversos itens de uma só vez. Como também não é permitido a um XML ter mais de um elemento como raiz, vou acrescentar uma nova raiz - produtos - e reconfigurar a tag de produto:
Os atributos minOccurs e maxOccus aplicados ao elemento produto determinam as quantidades mínima e máxima que esse elemento pode aparecer no XML. Caso esses atributos sejam omitidos, eles assumem o valor 1 (um); por isso, omití-los obriga sempre uma ocorrência do elemento. Valores válidos para esses atributos são números inteiros mas no caso do maxOccus podemos informar ainda o texto "unbounded" para indicar que não há limite máximo de ocorrências para o elemento. O trecho de XSD acima exemplifica ainda elementos complexos aninhados uns dentro dos outros, isto é, eles não precisam ser formados apenas por elementos simples.
O minOccurs também aceita o valor 0 (zero), o que na prática significa que o elemento em questão poderá ser omitido do XML - e, ainda assim, ele permanecerá válido. Isso abre a possibilidade de atribuirmos valores padronizados para elementos que não apareçam no XML.
No caso acima, se o elemento com o saldo do item não estiver presente no XML, devemos assumir que seu valor é zero.
Os tipos de dados com os quais definimos elementos no XSD constituem um tipo de restrição aos valores que esses elementos podem assumir no XML. No entanto, há situações em que só o tipo não é suficiente para determinar a validade de um dado. Para atender essa necessidade, um esquema XSD dispõe da tag xs:restriction, que, como o nome diz, serve para criar outras restrições aos valores permitidos em elementos simples num XML. A forma mais básica de restrição é a que estipula uma faixa de valores válidos:
O trecho acima reformula o elemento deposito para que ele aceite apenas códigos entre 1 e 99, sendo que esses valores-limites também são válidos. Repare que, apesar de continuar a ser um elemento simples (não são definidos elementos internos nele), o tipo do deposito agora foi passado para uma estrutura interna, delimitada por uma tag xs:simpleType, dentro da qual são explicitadas as novas restrições. Outros parâmetros válidos para configurar restrições podem ser encontrados neste link.
Outro tipo de restrição bastante comum é a lista de valores permitidos. Ou seja, para o XML ser válido, o valor do elemento nele tem que ser um daqueles listados na tag de restrição do XSD:
Como está exemplificado acima, cada item da lista de possíveis valores é definido através de uma tag xs:enumeration. Obviamente, os valores especificados na lista devem estar de acordo com o tipo base atribuído ao elemento - no caso apresentado acima, devem ser cadeias de caracteres (strings). Vale a pena ressaltar também que letras maiúsculas e minúsculas são consideradas diferentes e isso deve ser respeitado na hora de criar as declarações. Por exemplo, de acordo com os critérios do XSD acima, um valor un ou kg no elemento unidade do XML torna-o inválido visto que está prevista apenas a ocorrência desses valores em letras maiúsculas.
Alguns campos podem apresentar restrição de tamanho para serem válidos. No ERP da ABC71, por exemplo, a descrição de um produto deve ter no máximo 50 caracteres. Uma declaração para contemplar esse tipo de restrição pode ser feita como segue:
Além de maxLength e minLength - que definem uma faixa válida de quantidade de caracteres - há o elemento length, que serve para forçar uma quantidade exata de caracteres. Não a usei no exemplo porque isso obrigaria a colocar espaços em branco após as descrições de produto até que todos eles tivessem a quantidade exata exigida.
Há uma outra forma de restrição onde podemos estipular padrões de preenchimento dos campos, padrões estes baseados em máscaras mais ou menos como nas expressões regulares. Volto em outro post para falar sobre isso.
O defeito mais patente naquele exemplo é o fato de que apenas um produto por vez pode ser incluído no XML. Numa carga, faz mais sentido aceitar que se informem diversos itens de uma só vez. Como também não é permitido a um XML ter mais de um elemento como raiz, vou acrescentar uma nova raiz - produtos - e reconfigurar a tag de produto:
<xs:element name="produtos">
<xs:complexType>
<xs:sequence>
<xs:element name="produto" minOccurs="1" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="codigo" type="xs:string"/>
.
.
.
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType>
<xs:sequence>
<xs:element name="produto" minOccurs="1" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="codigo" type="xs:string"/>
.
.
.
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
Os atributos minOccurs e maxOccus aplicados ao elemento produto determinam as quantidades mínima e máxima que esse elemento pode aparecer no XML. Caso esses atributos sejam omitidos, eles assumem o valor 1 (um); por isso, omití-los obriga sempre uma ocorrência do elemento. Valores válidos para esses atributos são números inteiros mas no caso do maxOccus podemos informar ainda o texto "unbounded" para indicar que não há limite máximo de ocorrências para o elemento. O trecho de XSD acima exemplifica ainda elementos complexos aninhados uns dentro dos outros, isto é, eles não precisam ser formados apenas por elementos simples.
O minOccurs também aceita o valor 0 (zero), o que na prática significa que o elemento em questão poderá ser omitido do XML - e, ainda assim, ele permanecerá válido. Isso abre a possibilidade de atribuirmos valores padronizados para elementos que não apareçam no XML.
<xs:element name="saldo" type="xs:decimal" minOccurs="0" default="0"/>
No caso acima, se o elemento com o saldo do item não estiver presente no XML, devemos assumir que seu valor é zero.
Os tipos de dados com os quais definimos elementos no XSD constituem um tipo de restrição aos valores que esses elementos podem assumir no XML. No entanto, há situações em que só o tipo não é suficiente para determinar a validade de um dado. Para atender essa necessidade, um esquema XSD dispõe da tag xs:restriction, que, como o nome diz, serve para criar outras restrições aos valores permitidos em elementos simples num XML. A forma mais básica de restrição é a que estipula uma faixa de valores válidos:
<xs:element name="deposito" >
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="1"/>
<xs:maxInclusive value="99"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="1"/>
<xs:maxInclusive value="99"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
O trecho acima reformula o elemento deposito para que ele aceite apenas códigos entre 1 e 99, sendo que esses valores-limites também são válidos. Repare que, apesar de continuar a ser um elemento simples (não são definidos elementos internos nele), o tipo do deposito agora foi passado para uma estrutura interna, delimitada por uma tag xs:simpleType, dentro da qual são explicitadas as novas restrições. Outros parâmetros válidos para configurar restrições podem ser encontrados neste link.
Outro tipo de restrição bastante comum é a lista de valores permitidos. Ou seja, para o XML ser válido, o valor do elemento nele tem que ser um daqueles listados na tag de restrição do XSD:
<xs:element name="unidade" maxOccurs="5">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="UN"/>
<xs:enumeration value="CX"/>
<xs:enumeration value="KG"/>
<xs:enumeration value="PC"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="UN"/>
<xs:enumeration value="CX"/>
<xs:enumeration value="KG"/>
<xs:enumeration value="PC"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
Como está exemplificado acima, cada item da lista de possíveis valores é definido através de uma tag xs:enumeration. Obviamente, os valores especificados na lista devem estar de acordo com o tipo base atribuído ao elemento - no caso apresentado acima, devem ser cadeias de caracteres (strings). Vale a pena ressaltar também que letras maiúsculas e minúsculas são consideradas diferentes e isso deve ser respeitado na hora de criar as declarações. Por exemplo, de acordo com os critérios do XSD acima, um valor un ou kg no elemento unidade do XML torna-o inválido visto que está prevista apenas a ocorrência desses valores em letras maiúsculas.
Alguns campos podem apresentar restrição de tamanho para serem válidos. No ERP da ABC71, por exemplo, a descrição de um produto deve ter no máximo 50 caracteres. Uma declaração para contemplar esse tipo de restrição pode ser feita como segue:
<xs:element name="descricao">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="50"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="50"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
Além de maxLength e minLength - que definem uma faixa válida de quantidade de caracteres - há o elemento length, que serve para forçar uma quantidade exata de caracteres. Não a usei no exemplo porque isso obrigaria a colocar espaços em branco após as descrições de produto até que todos eles tivessem a quantidade exata exigida.
Há uma outra forma de restrição onde podemos estipular padrões de preenchimento dos campos, padrões estes baseados em máscaras mais ou menos como nas expressões regulares. Volto em outro post para falar sobre isso.
6 de dezembro de 2010
Criando Esquema para validar um XML - parte II
Depois de montar o cabeçalho do Esquema XSD, onde definimos um namespace e outros parâmetros para evitar choque de nomes com outros esquemas, podemos abordar a criação das regras de validação que orientarão como os elementos do XML devem ser criados.
Elementos são definidos num XSD usando-se a tag xs:element, que deve ser inserida entre a abertura e o encerramento da tag de cabeçalho. Um XSD pode definir basicamente dois tipos de elementos para o XML: os que contêm outros elementos aninhados são chamados de Complexos; os demais são chamados de Simples. O nó raiz do XML necessariamente será do tipo Complexo já que sempre haverão outros elementos aninhados nele.
Fica mais fácil trabalhando com um exemplo. Suponha, então, que estamos estabelecendo a estrutura de um XML a ser usado para carga do cadastro de produtos. Neste XML, as informações básicas do produto podem ser definidas no XSD como elementos simples. Segue um trecho extraído do XSD com essas definições:
As linhas acima definem 4 elementos simples que devem aparecer no XML de produtos. A cada elemento é atribuído um nome e um tipo de dado - por enquanto, usei apenas tipos preestabelecidos pela W3C. Assim, o XML será considerado inválido se um elemento com qualquer outro nome estiver inserido nele ou se o tipo de dado esperado para cada elemento não corresponder à definição. O trecho de XML reproduzido abaixo está em conformidade com o XSD anterior:
Os tipos complexos do XSD encapsulam declarações como as que fizemos no início deste post. Ao fazer isso, estabelecem também qual a sequência em que os elementos devem estar inseridos no XML. Declarar um elemento do tipo complexo exige o uso de duas novas tags: o xs:complexType - que substitui o atributo "type" usado para declarar os tipos simples - e o xs:sequence - que vai embutido no complexType e que encorpora as declarações dos demais elementos que compõem esse tipo complexo. Veja como fica a declaração do XSD para o exemplo do produto:
Repare que a tag inicial é do tipo xs:element pois estamos definindo um novo elemento, ainda que do tipo complexo. O atributo name também é exigido aqui, enquanto o type foi eliminado para permitir que a declaração dos elementos seja feita de forma aninhada. O exemplo mostra apenas elementos de tipo simples aninhados no tipo complexo mas isso não é uma obrigação. Se for necessário, podemos aninhar outros tipos complexos também.
Como não estabelecemos nenhuma regra extra, todos os elementos definidos no nosso XSD tem que aparecer uma e somente uma vez no XML, isto é, esses elementos não podem ser omitidos nem aparecer duplicados. Caso contrário, o XML será considerado inválido.
Além disso, da forma como está declarado neste post, o XSD permite que apenas um produto seja incluído no XML. Num post futuro eu avanço no assunto, introduzindo novos conceitos para melhorar a declaração de tipos e enriquecer as validações, tornando-as mais precisas.
Elementos são definidos num XSD usando-se a tag xs:element, que deve ser inserida entre a abertura e o encerramento da tag de cabeçalho. Um XSD pode definir basicamente dois tipos de elementos para o XML: os que contêm outros elementos aninhados são chamados de Complexos; os demais são chamados de Simples. O nó raiz do XML necessariamente será do tipo Complexo já que sempre haverão outros elementos aninhados nele.
Fica mais fácil trabalhando com um exemplo. Suponha, então, que estamos estabelecendo a estrutura de um XML a ser usado para carga do cadastro de produtos. Neste XML, as informações básicas do produto podem ser definidas no XSD como elementos simples. Segue um trecho extraído do XSD com essas definições:
<xs:element name="codigo" type="xs:string"/>
<xs:element name="deposito" type="xs:integer"/>
<xs:element name="saldo" type="xs:decimal"/>
<xs:element name="data_criacao" type="xs:date"/>
<xs:element name="deposito" type="xs:integer"/>
<xs:element name="saldo" type="xs:decimal"/>
<xs:element name="data_criacao" type="xs:date"/>
As linhas acima definem 4 elementos simples que devem aparecer no XML de produtos. A cada elemento é atribuído um nome e um tipo de dado - por enquanto, usei apenas tipos preestabelecidos pela W3C. Assim, o XML será considerado inválido se um elemento com qualquer outro nome estiver inserido nele ou se o tipo de dado esperado para cada elemento não corresponder à definição. O trecho de XML reproduzido abaixo está em conformidade com o XSD anterior:
<abc:codigo>000.001-8</abc:codigo>
<abc:deposito>1</abc:deposito>
<abc:saldo>220.34</abc:saldo>
<abc:data_criacao>2010-01-20</abc:data_criacao>
<abc:deposito>1</abc:deposito>
<abc:saldo>220.34</abc:saldo>
<abc:data_criacao>2010-01-20</abc:data_criacao>
Os tipos complexos do XSD encapsulam declarações como as que fizemos no início deste post. Ao fazer isso, estabelecem também qual a sequência em que os elementos devem estar inseridos no XML. Declarar um elemento do tipo complexo exige o uso de duas novas tags: o xs:complexType - que substitui o atributo "type" usado para declarar os tipos simples - e o xs:sequence - que vai embutido no complexType e que encorpora as declarações dos demais elementos que compõem esse tipo complexo. Veja como fica a declaração do XSD para o exemplo do produto:
<xs:element name="produto">
<xs:complexType>
<xs:sequence>
<xs:element name="codigo" type="xs:string"/>
<xs:element name="deposito" type="xs:integer"/>
<xs:element name="saldo" type="xs:decimal"/>
<xs:element name="data_criacao" type="xs:date"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType>
<xs:sequence>
<xs:element name="codigo" type="xs:string"/>
<xs:element name="deposito" type="xs:integer"/>
<xs:element name="saldo" type="xs:decimal"/>
<xs:element name="data_criacao" type="xs:date"/>
</xs:sequence>
</xs:complexType>
</xs:element>
Repare que a tag inicial é do tipo xs:element pois estamos definindo um novo elemento, ainda que do tipo complexo. O atributo name também é exigido aqui, enquanto o type foi eliminado para permitir que a declaração dos elementos seja feita de forma aninhada. O exemplo mostra apenas elementos de tipo simples aninhados no tipo complexo mas isso não é uma obrigação. Se for necessário, podemos aninhar outros tipos complexos também.
Como não estabelecemos nenhuma regra extra, todos os elementos definidos no nosso XSD tem que aparecer uma e somente uma vez no XML, isto é, esses elementos não podem ser omitidos nem aparecer duplicados. Caso contrário, o XML será considerado inválido.
Além disso, da forma como está declarado neste post, o XSD permite que apenas um produto seja incluído no XML. Num post futuro eu avanço no assunto, introduzindo novos conceitos para melhorar a declaração de tipos e enriquecer as validações, tornando-as mais precisas.

