Resumo:
Este documento tem como objectivo fazer uma pequena abordagem á resolução do problema proposto na 1ª fase do trabalho prático.
O objectivo desta 1ª fase do trabalho é o de implementar um ferramenta que permita reconhecer documentos em XML. Além do reconhecimento, foi já implementado um grove que permite guardar os dados á medida que vão sendo processados. Finalmente a estrutura pode ser percorrida por uma função que faz a travessia e que gera em linguagem ESIS a representação dos dados reconhecidos.
O problema proposto consistia na criação de um reconhecedor de textos num formato que obedecia a uma determinada gramática, gramática essa, que descreve a sintaxe do XML. Posteriormente foi-nos pedido que guardássemos os dados num grove, isto é, que encontrassemos uma estrutura de dados que permitisse representar todo o documento respeitando a sua estrutura inicial. Foram-nos dados 3 prazos para acabar 3 etapas de resolução do reconhecimento. A primeira etapa consistiu na validação da sintaxe de frases XML, a segunda consistiu na validação semântica, e a terceira consistiu no armazenamento da informação.
Nesta etapa tinhamos a tarefa de implementar um analisador léxico. Para tal efeito, usamos as ferramentas Lex e Yacc em conjunto. O Lex foi utilizado para reconhecer os diferentes padrões dos documentos. Depois de reconhecidos, esses padrões eram enviados ao Yacc, que tinha a função de verificar se a ordem pela qual apareciam respeitava a gramática, por outras palavras, se a sequência de símbolos encontrados pelo Lex pertencia á Linguagem gerada pela gramática. A análise léxica não foi passiva. Por uma questão de economia e respeitando sempre a integridade da informação, foram filtrados todos os espaços em branco que existiam entre tags. Desta forma evitamos a existencia de blocos de texto em branco na estrutura de dados.
Na implementação da gramaática no Yacc, obtivemos alguns shift/reduce conflicts . Estes conflitos foram resolvidos alterando a recursividade numa das produções para a esquerda, quando ela, no inicio, estava á direita. Também por uma questão de optimização alteramos uma das produções, de maneira a não receber um caracter de cada vez, mas sim blocos de texto. Desta maneira optimizamos a retenção da informação no grove, pois assim temos listas de textos e/ou tags, e não listas de caracteres e/ou tags.
Tinhamos agora a tarefa de verificar se todas as tags abertas eram fechadas, e se respeitavam a ordem pela qual iam aparecendo (a última a ser aberta seria a primeira a ser fechada). Para resolver este problema, no Yacc, bastou-nos comparar os campos dos identificadores de uma abertura e de um fecho de tag. A gramática por sua vez obrigava a que todas as tags que fossem abertas, posteriormente viessem a ser fechadas. Além disso a gramática obriga também que todo o texto do documento esteja entre tags.
Nesta última etapa procedeu-se á implementação de um grove que guarda a informação processada. A estrutura do grove assemelha-se a um bloco de campos, 4 mais especificamente, os quais guardam apontadores para a informação. Assim temos:
campo que contem o nome da tag ou um identificador reservado para guardar texto livre.
campo que contem o apontador para o conteúdo deste grove específico. Assim podemos ter um apontador para tags filhas ou texto livre aqui pendurados.
campo que contem o apontador para a lista de atributos no caso de se estar a tratar uma tag. No caso de se estar a tratar texto este apontador é nulo (NULL).
campo que contem o apontador para os blocos de informação irmãos.
Para melhor visualização, fornecemos aqui uma imagem que de certa maneira representa do grove, assim como a sua estrutura interna.
Figura:
Para armazenar o texto livre do documento, usamos o campo laranja, para armazenar tags filhas da tag inicial, usamos um apontador para um novo bloco azul, como está demonstrado no esquema.
Recorremos ás Glibs para guardar o texto. Assim o campo texto é na realidade uma GString. Com as GString temos um conjunto de funções disponíveis para melhor e mais fácil manuseamento de strings.
Doc -> '<' id AttrList '>' ElemList '<' '/' id '>'
ElemList -> ElemList Elem
| &
Elem -> texto
| '&' id ';'
| '<' id AttrList '/' '>'
| '<' id AttrList '>' ElemList '<' '/' id '>'
AttrList -> AttrList Attr
| &
Attr -> id '=' valor
typedef struct _attr_ { // struct que define a estrutura de um atributo
char * nome; // nome do atributo
char * valor; // valor do atributo
struct _attr_ * next; // apontador para o atributo seguinte
} attr;
typedef struct _tag_ { // struct que contem os campos a utilizar para guardar toda a infomrmacao necessaria
char * tag_id; // identificador de tag
union _cont_ { // o conteudo da esrutura
GString * texto; // ou e texto
struct _tag_ * filho; // ou e um apontador para os filhos
} conteudo; // a union chama-se conteudo
attr * atributos; // lista de atributos
struct _tag_ * next; // apontador para a next tag
} tag;
#include "grove.h"
tag * newgrovenode (char * nome, attr * la) {
tag * g = (tag *) malloc (sizeof(tag));
g->tag_id = nome;
g->atributos = la;
g->next = NULL;
return (g);
}
attr * newatribnode(char * n, char * v) {
attr * al = (attr *) malloc (sizeof(attr));
al->nome = n;
al->valor = v;
al->next = NULL;
return (al);
}
void printatribs (attr * al) {
char * s1;
while (al) {
s1 = strdup((al->valor)+sizeof(char));
s1[(strlen(s1)-sizeof(char))]='\0';
printf("A %s %s\n", al->nome, s1);
al = al->next;
}
}
tag * update_texto(tag * t, GString * s) {
t->conteudo.texto = s;
return(t);
}
tag * update_filho(tag * t, tag * t_filho) {
t->conteudo.filho = t_filho;
return(t);
}
tag * last(tag * t) {
tag * aux = t;
if(aux) {
while (aux->next) aux=aux->next;
}
return(aux);
}
void travessia (tag * g){
tag * aux = g;
int flag = 0;
while(aux) {
if (strcmp(aux->tag_id, "_rtext-id_")==0 ) {
if(flag) printf("%s",aux->conteudo.texto->str);
else printf ("\n-%s", aux->conteudo.texto->str);
aux = aux->next;
flag=1;
}
else {
printf("\n");
flag=0;
printatribs(aux->atributos);
printf("( %s", aux->tag_id);
travessia(aux->conteudo.filho);
printf("\n)/ %s",aux->tag_id);
aux=aux->next;
}
}
}
%{
#include <glib.h>
#include <string.h>
int n_lines=1, tag_n=1;
GString * gstr=NULL;
%}
id [a-zA-Z][a-zA-Z0-9\-]*
branco [ \t]*
valor \"[^\"]+\"
texto [^<&]+
%x TAG ACC
%%
& { if (gstr) {
yylval.string=gstr;
gstr=NULL;
unput('&');
return(texto);
} else { BEGIN(ACC); return(*yytext); }
}
\< { if (gstr) {
yylval.string=gstr;
gstr=NULL;
unput ('<');
return(texto);
} else { BEGIN(TAG); tag_n++; return(*yytext); }
}
<ACC>; { BEGIN(0); return(*yytext); }
<TAG>{branco} ;
<TAG>{id} { yylval.str = (char *) strdup(yytext); return(id); }
<TAG>{valor} { yylval.str = (char *) strdup(yytext); return(valor); }
<TAG>> { BEGIN(0); return(*yytext); }
<TAG>>[ \n\t\r]+< { BEGIN(0); // Limpa os espaços brancos entre tags
unput('<');
return(*yytext);
}
<TAG>[\/=] return(*yytext);
<TAG>\n n_lines++;
<TAG,ACC>{id} { yylval.str = (char *) strdup(yytext); return(id); }
{texto} { if (conta(yytext)) { n_lines+=conta(yytext); tag_n=0; }
if (!gstr) gstr=g_string_new("");
gstr = g_string_append(gstr,yytext);
}
%%
%{
#include <glib.h>
#include <stdio.h>
#include <stdlib.h>
#include "grove.h"
/* Variaveis globais auxiliares */
int warnings=0;
attr * lista_atribs=NULL;
attr * la_aux=NULL;
tag * tag_aux=NULL;
tag * idoc=NULL;
GString * straux=NULL;
gchar * s;
/* Contador de linhas */
extern int n_lines;
/* Funcao que conta o numero de linhas */
int conta(char* line) {
char* a;
int c=0;
a=line;
while(*line)
{
if(*line=='\n') c++;
line++;
}
return c;
}
%}
%token <str> id valor
%token <string> texto
%union {
char * str;
attr * la;
tag * t;
GString * string;
char c;
}
%type <la> Attr AttrList
%type <t> Elem ElemList
%start Doc
%%
Doc : '<' id AttrList '>' ElemList '<' '/' id '>'
{
tag_aux = newgrovenode($2,$3);
tag_aux->conteudo.filho = $5;
idoc = tag_aux;
if(strcmp($2,$8)!=0) {
warnings++;
printf("Warning: line %d: Tag number %d: Tag names mismatch!!!\t( Expected Tag name : %s )\n",n_lines,tag_n,$2);
}
if(warnings) printf("Total Warnings: %d\n",warnings);
else printf("Ok!!\n\n");
travessia(idoc);
}
;
ElemList : ElemList Elem {
if ($1!=NULL) {
last($1)->next = $2;
$$=$1; }
else $$=$2;
}
| { $$ = NULL;}
;
Elem : texto {
tag_aux = newgrovenode("_rtext-id_", NULL);
tag_aux = (tag *) update_texto(tag_aux,$1);
$$ = tag_aux;
}
| '&' id ';' {
s = g_strconcat("&",$2,";");
tag_aux = newgrovenode("_rtext-id_", NULL);
tag_aux = (tag *) update_texto(tag_aux,g_string_new(s));
$$ = tag_aux;
}
| '<' id AttrList '/' '>' { tag_aux = newgrovenode($2, $3);
tag_aux->conteudo.filho = NULL;
$$ = tag_aux;
}
| '<' id AttrList '>' ElemList '<' '/' id '>'
{
if(strcmp($2,$8)!=0) {
warnings++; printf("Warning: line %d: Tag number %d: Tag names mismatch!!!\t( Expected Tag name : %s )\n",n_lines,tag_n,$2);
}
tag_aux = newgrovenode($2,$3);
tag_aux->conteudo.filho = $5;
$$ = tag_aux;
}
;
AttrList : AttrList Attr {
if ($1!=NULL) {
la_aux=$1;
while(la_aux->next!=NULL) {la_aux=la_aux->next;}
la_aux->next = $2;
$$=$1;
}
else $$=$2;
}
| { $$ = NULL; }
;
Attr : id '=' valor { lista_atribs = newatribnode($1,$3);
$$ = lista_atribs;
}
;
%%
#include "xml.fl.c"
yyerror( char * s ) { printf("%s: linha numero %d!!\n",s,n_lines); }
A conclusão desta primeira fase permite-nos, não só tomar consciência de que existem formas de automatizar a construção de analisadores de documentos estruturados, como ter uma visão diferente de um parte muito particular de problemas, que é a de trabalhar com texto como tipo de dados.
De realçar ainda que o que nos parecia algo difícil, como guardar o documento num grove, tornou-se surpreendentemente bastante mais fácil. Este problema foi resolvido utilizando funções simples e fáceis de implementar, pois o trabalho propriamente dito foi feito pelo Yacc, que utilizando estas funções nas suas acções semânticas, foi construindo o grove final.
Queriamos agradecer aqueles que realmente sabem que merecem os nossos agradecimentos. A todos aqueles que tornaram tudo isto possível, sem os quais estrariamos perdidos na escuridão da ignorância, o nosso sincero obrigado. Não é preciso citar nomes pois eles sabem, eles andam aí...
Bibliografia: