Usando XML Schemas em C

Há 3 anos escrevi um pequeno artigo por aqui sobre usar XMLs em C, usando a biblioteca libxml2. Minha intenção era preparar o terreno para usar o DOM (Document Object Model) do COLLADA no meu projeto VirtualWorlds.

XML é uma espécie de banco de dados, só que hierárquico. Isso significa que podemos pendurar outros “nós” e “atributos” em nós já estabelecidos. Fazer isso é trabalhar com uma forma não estruturada do XML. Mas, e se quiséssemos uma estrutura estrita? Ou seja, e se um conjunto de regras fossem necessárias para a construção e interpretação do “banco de dados” (do arquivo XML)?

Para isso o XML tem um padrão chamado XSD (XML Schema Definition), ou simplesmente Schemas.

A idéia é criar um arquivo XML que descreve a estrutura do arquivo XML que conterá os seus dados. Assim, ao tentar criar alguma estrutura ad hoc, o Schema Parser reclama e não deixa você fazê-lo. Eis um pequeno exemplo de um validador, usando schemas, em C:

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string.h>

#include <libxml/parser.h>
#include <libxml/valid.h>
#include <libxml/xmlschemas.h>

static unsigned long get_file_size(const char *file_name)
{
  struct stat buf;

  if ( stat(file_name, &buf) != 0 )
    return 0;

  return (unsigned long)buf.st_size;
}

static void handleValidationError(void *ctx, const char *format, ...)
{
  char *errMsg;
  va_list args;

  va_start(args, format);
  vasprintf(&errMsg, format, args);
  va_end(args);

  fprintf(stderr, "Validation Error: %s", errMsg);

  free(errMsg);
}

int main(int argc, const char *argv[])
{
  unsigned long xmlLength;
  char *xmlSource;
  FILE *fp;
  int result;
  xmlSchemaParserCtxtPtr parserCtx = NULL;
  xmlSchemaPtr schema = NULL;
  xmlSchemaValidCtxtPtr validCtx = NULL;
  xmlDocPtr xmlDocumentPointer;

  if (argc != 3)
  {
    fprintf(stderr, "Usage: check <schema> <xml>\n");
    return EXIT_FAILURE;
  }

  /* argv[1] = xsd path, argv[2] = xml path. */
  printf("XSD File: %s\n", argv[1]);
  printf("XML File: %s\n", argv[2]);

  if ((xmlLength = get_file_size(argv[2])) == 0)
  {
    fprintf(stderr, "XML file is empty?!");
    return EXIT_FAILURE;
  }

  if ((xmlSource = (char *)malloc(sizeof(char) * (xmlLength + 1))) == NULL)
  {
    fprintf(stderr, "Error allocating buffer for xml file.\n");
    return EXIT_FAILURE;
  }

  fp = fopen(argv[2], "r");
  result = 42;

  if (fp == NULL || (fread(xmlSource, xmlLength, 1, fp) <= 0))
  {
    fprintf(stderr, "Error reading xml file.\n");
    fclose(fp);
    return EXIT_FAILURE;
  }

  /* Marca o fim da string com 0 */
  *(xmlSource + xmlLength) = '';

  fclose(fp);

  printf("XML Source:\n\n%s\n\n", xmlSource);

  if ((xmlDocumentPointer = xmlParseMemory(xmlSource, xmlLength)) == NULL)
  {
    fprintf(stderr, "Error parsing xml file.\n");
    return EXIT_FAILURE;
  }

  /* Poderíamos ter usado xmlSchemaNewMemParserCtxt() aqui,
     como fizemos com xmlParseMemory(), acima. */

  if ((parserCtx = xmlSchemaNewParserCtxt(argv[1])) == NULL)
  {
    fprintf(stderr, "Could not create XSD schema parsing context.\n");
    goto leave;
  }

  if ((schema = xmlSchemaParse(parserCtx)) == NULL)
  {
    fprintf(stderr, "Could not parse XSD schema.\n");
    goto leave;
  }

  if ((validCtx = xmlSchemaNewValidCtxt(schema)) == NULL)
  {
    fprintf(stderr, "Could not create XSD schema validation context.\n");
    goto leave;
  }

  /* Registro de funções de tratamento de erros para o validaddor de schemas. */
  xmlSetStructuredErrorFunc(NULL, NULL);
  xmlSetGenericErrorFunc(NULL, handleValidationError);
  xmlThrDefSetStructuredErrorFunc(NULL, NULL);
  xmlThrDefSetGenericErrorFunc(NULL, handleValidationError);

  /* Finalmente valida o XML com o Schema. */
  result = xmlSchemaValidateDoc(validCtx, xmlDocumentPointer);

leave:
  if (parserCtx)
    xmlSchemaFreeParserCtxt(parserCtx);

  if (schema)
    xmlSchemaFree(schema);

  if (validCtx)
    xmlSchemaFreeValidCtxt(validCtx);

  printf("Validation successful: %s (result: %d)\n", (result == 0) ? "YES" : "NO", result);

  return EXIT_SUCCESS;
}

Parece complicado, mas o que o código acima faz, essencialmente, é:

  1. Carrega o arquivo XML e cria o container xmlDocumentPointer (do tipo xmlDocPtr);
  2. Carrega o XSD e cria o container/parser parserCtx (do tipo xmlShemaParserCtxtPtr).
  3. Cria (e valida) o esquema via schema (do tipo xmlSchemaPtr);
  4. Cria o validador de XMLs através de schema (do tipo xmlSchemaValidCtxtPtr);
  5. Chama xmlSchemaValidateDoc(), usando o validador e o xmlDocPtr.

Eis um exemplo dos arquivos XML. Primeiro, o XML de teste:

<?xml version="1.0" encoding="UTF-8"?>
<foo xmlns="http://example.com/XMLSchema/1.0">
</foo>

Segundo, o schema:

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE xs:schema PUBLIC "-//W3C//DTD XMLSCHEMA 200102//EN" "XMLSchema.dtd" >
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
           xmlns="http://example.com/XMLSchema/1.0" 
           targetNamespace="http://example.com/XMLSchema/1.0" 
           elementFormDefault="qualified" 
           attributeFormDefault="unqualified">
    <xs:element name="foo">
    </xs:element>
</xs:schema>

Ao usar um schema, se você tentar mudar a estrutura do arquivo XML, de forma que não esteja prevista no arquio XSD, o código vai indicar um erro.

A documentação da libxml2 pode ser encontrada aqui.

Anúncios

Um comentário sobre “Usando XML Schemas em C

Deixe um comentário

Faça o login usando um destes métodos para comentar:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s