Resumo:
Este trabalho surge no âmbito da disciplina de Processamento de Linguagens I , e consiste no reconhecimento de um documento XML.
O objectivo deste trabalho é a utilização das ferramentas lex e yacc para fazer o reconhecimento de ficheiros XML.
Nesta primeira fase do trabalho foram criados dois ficheiros: um em lex e outro em yacc, respectivamente o analisador léxico e sintáctico. Além disso, implementámos a estrutura (grove) que deverá guardar a informação, ficando esta armazenada em memória principal.
O analisador léxico não trouxe grandes problemas ao nível da implementação. Aquilo que optámos por fazer foi separar dois casos. Um deles é o caracter < simbolizando a abertura de uma nova tag. Neste caso, aquilo que fizemos foi entrar numa condição de contexto condicionando assim o que poderia suceder àquele caracter. O outro caso é o do caracter ">" que simboliza o fim de uma tag. Neste caso, voltamos ao ínicio através de um BEGIN(0). Tomámos a opção de não considerar o '&' ao contrário do que vem especificado na gramática, porque não estaria de acordo com a estrutura de dados que adoptámos, e a sua identificação não era necessária. Esta escolha foi discutida com o Prof. Ramalho, que concordou com a nossa decisão. Como futuro desenvolvimento, pretendemos incluir um contador de linhas de forma a detectarmos a linha onde ocorreu o erro e corrigir a forma como detectamos o texto, já que, quando o texto atinge um determinado número de caracteres, é dividido em blocos.
No analisador sintáctico, em primeiro lugar, alterámos a gramática inicialmente proposta. Criámos uma que "exigisse" a existência de uma abertura e um fecho idênticos, para "identificar" o tipo de documento (ex: XML, PLI-DOC, HTML, etc). Verificámos também que todas as tags fechavam pela ordem inversa à que abriam. Fizemos isto sem recorrer a uma stack, comparando os identificadores à medida que aparecem. Também aqui reconhecemos como texto dados com o formato "&aaa;", tal como na análise léxica.
Em relação à estrutura de dados, criàmos duas struct's. Uma delas para os atributos, e a outra para os nodos. Os nodos guardam a informação relativa às tags, ou ainda texto. Neste último caso, o campo do identificador fica com o valor "#TXT", de maneira a sabermos qual o campo da union a usar. No caso de esse campo ser diferente de "#TXT", usamos o campo cont que representa o conteúdo deste nodo. Caso contrário, usamos o campo txt que contém o texto. Para além do identificador,em cada nodo temos ainda um apontador para a lista de atributos, e outro para os seus irmãos. Os atributos têm apenas dois campos de informação contendo o tipo de atributo e o seu valor, e um apontador para o próximo atributo.
Exemplo:
typedef struct a{
char *nome;
char *valor;
struct a *next;
} *Atributo;
typedef struct n{
char* id;
union {
struct n* cont;
char* txt;
} z;
Atributo attr;
struct n* next;
} *Nodo;
ID [A-Za-z][A-Za-z0-9\-\_]*
TXT [^<]+
VALOR ["][^ \n\t]+["]
VARIOS [=/]
%x abrir
%%
^<abrir,INITIAL>[ \t\n]+ ;
\^< {BEGIN(abrir);return(*yytext);}
<abrir>{ID} {yylval.id=(char*)strdup(yytext);return(ID);}
<abrir>{VARIOS} {return(*yytext);}
<abrir>{VALOR} {yylval.valor=(char*)strdup(yytext);return(VALOR);}
<abrir>\> {BEGIN(0);return(*yytext);}
{TXT} {yylval.txt=(char*)strdup(yytext);return(TXT);}
%%
%{
#include <string.h>
#include "grove.h"
Nodo grove;
%}
%token VALOR ID TXT
%start DOCXML
%union{
char *id;
char *valor;
char *txt;
Nodo n;
Atributo attr;
}
%type <id> ID FECHO
%type <valor> VALOR
%type <txt> TXT
%type <n> ABERTURA ELEM ELEMS ABREFECHO
%type attr> ATRIBS ATRIB
%%
DOCXML:ABERTURA ELEMS FECHO {grove=$1;
grove->z.cont=$2;
if (strcmp(grove->id,$3)) yyerror("erro de tags");
else printf ("\nReconheci um documento estruturado!\n\n");};
ABERTURA:'<' ID ATRIBS '>' {$$=criaNodo($2,$3);};
ATRIBS: ATRIB {$$=$1;};
| ATRIBS ATRIB {fa($1,$2);$$=$1;};
| ; {$$=NULL;};
ATRIB: ID '=' VALOR {$$=criaAtrib($1,$3);};
ELEMS:ELEM {$$=$1;};
|ELEMS ELEM {f($1,$2);
$$=$1;};
ELEM:TXT {$$=criaTxt($1);};
|ABREFECHO {$$=$1;};
|ABERTURA ELEMS FECHO {($1)->z.cont=$2;
$$=$1;
if (strcmp($1->id,$3)) yyerror("erro de tags");};
FECHO:'<' '/' ID '>' {$$=$3;};
ABREFECHO:'<' ID ATRIBS '/' '>' {$$=criaNodo($2,$3);};
%%
#include "lex.yy.c"
int yyerror(char *s) {
printf("\nSomething´s wrong...:((( -> %s\n\n",s);
exit(1);
}
int main(){
yyparse();
travessia(grove);
}
yywrap(){}
#include <stdio.h>
#include <stdlib.h>
typedef struct a{
char *nome;
char *valor;
struct a *next;
} *Atributo;
typedef struct n{
char* id;
union {
struct n* cont;
char* txt;
} z;
Atributo attr;
struct n* next;
} *Nodo;
Nodo criaTxt(char *txt){
Nodo n;
n=(Nodo)calloc(1, sizeof(Nodo));
n->id="#TXT";
n->z.txt=strdup(txt);
n->attr=NULL;
n->next=NULL;
return n;
}
Atributo criaAtrib(char *id, char *valor) {
Atributo a;
a=(Atributo)calloc(1,sizeof(Atributo));
a->nome=strdup(id);
a->valor=strdup(valor);
a->next=NULL;
return a;
}
Nodo criaNodo(char *id, Atributo atribs){
Nodo n;
n=(Nodo)calloc(1,sizeof(Nodo));
n->id=strdup(id);
n->attr=atribs;
n->z.cont=NULL;
n->next=NULL;
return n;
}
void ver_atr(Atributo a) {
if(a) {
printf("A %s %s\n",a->nome,a->valor);
ver_atr(a->next);
}
}
void f(Nodo n1, Nodo n2) {
Nodo n3=n1;
while(n3->next) n3=n3->next;
n3->next=n2;
}
void fa(Atributo n1, Atributo n2) {
Atributo n3=n1;
while(n3->next) n3=n3->next;
n3->next=n2;
}
void travessia(Nodo n) {
if(n) {
if (!strcmp(n->id,"#TXT")) {
printf("-%s\n",n->z.txt);
}
else { ver_atr(n->attr);
printf("(%s\n",n->id);
travessia(n->z.cont);
printf(")%s\n",n->id);
}
travessia(n->next);
}
}
Queremos agradecer ao professor José Carlos Ramalho e ao professor Pedro Henriques por esclarecerem as dúvidas que foram surgindo ao longo desta primeira fase.
Bibliografia: