Mostrando postagens com marcador Parser. Mostrar todas as postagens
Mostrando postagens com marcador Parser. Mostrar todas as postagens

sábado, 14 de abril de 2012

iOS: Lendo XML

Olá povo,

Neste post vou mostrar como ler arquivos XML em aplicações iOS. O XML que estou lendo, eu baixei no site do livro Google Android do Ricardo Lecheta (eu tento, mas não deixo o Android :)
O modelo do estrutura do XML que iremos ler é apresentado abaixo:
<?xml version="1.0" encoding="utf-8"?>
<carros>
  <carro>
    <nome>Modelo do Carro</nome>
    <desc>Descrição do Carro</desc>
    <url_info>http://www.ferrari.com/</url_info>
    <url_foto>http://www.livroandroid.com.br/</url_foto>
  </carro>
  ...
</carros>
Crie um novo projeto no Xcode utilizando o template "Single View Application". Agora acesse o site do livro e baixe o XML, em seguida adicione-o ao seu projeto no Xcode na pasta "Supporting Files". A estrutura do projeto (no final) deve ficar conforme abaixo:

Como você deve ter observado, o carro descrito no XML tem os atributos nome, desc, url_info e url_foto. Vamos criar uma classe que representará esse objeto em código.
// Carro.h ---------------------------------------
#import <Foundation/Foundation.h>

@interface Carro : NSObject

@property (strong, nonatomic) UIImage *image;
@property (strong, nonatomic) NSString *nome;
@property (strong, nonatomic) NSString *descricao;
@property (strong, nonatomic) NSString *urlInfo;
@property (strong, nonatomic) NSString *urlFoto;

@end
// Carro.m ---------------------------------------
#import "Carro.h"

@implementation Carro

@synthesize nome, descricao, urlFoto, urlInfo, image;

@end
Vou alterar o AppDelegate da aplicação para ficar desta forma:
// NGAppDelegate.h -------------------------------
#import <UIKit/UIKit.h>

@class NGViewController;

@interface NGAppDelegate : 
  UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) 
  UIWindow *window;
@property (strong, nonatomic) 
  UINavigationController *navigationController;

@end
// NGAppDelegate.m -------------------------------
#import "NGAppDelegate.h"
#import "NGViewController.h"

@implementation NGAppDelegate

@synthesize window = _window;
@synthesize navigationController;

- (BOOL)application:
   (UIApplication *)application
     didFinishLaunchingWithOptions:
   (NSDictionary *)launchOptions{

  self.window = [[UIWindow alloc]
     initWithFrame:[[UIScreen mainScreen] bounds]];

  NGViewController *viewController =
     [[NGViewController alloc] initWithNibName:
       @"NGViewController" bundle:nil];

  navigationController =
     [[UINavigationController alloc]
       initWithRootViewController:viewController];

  self.window.rootViewController =
     self.navigationController;

  [self.window makeKeyAndVisible];
  return YES;
}
// Outros métodos não alterados
@end
Nesse template, a aplicação é criada com uma UIViewController simples. Vamos utilizar uma UITableViewController que listará os dados lidos através de um objeto da class NSXMLParser. Sua utilização é bem similar ao SAX do Java, e teremos que implementar um protocolo NSXMLParserDelegate, e a medida que as tags são lidas os métodos desse protocolo são chamados.
#import <UIKit/UIKit.h>
#import "Carro.h"

@interface NGViewController : UITableViewController
   <NSXMLParserDelegate>{

  NSMutableArray *carros;
  Carro *carro;
  NSString *currTag;
}

@end
Um NSMutableArray armazenará os objetos Carro criados a partir da leitura do XML. Durante o parser do XML, precisamos saber que tag está sendo lida, para tal setarmos o atributo currTag com tag que corresponde a propriedade da classe Carro. Por exemplo, quando o parser encontrar uma tag "carro", criamos um objeto Carro, já quando encontramos uma tag "nome", devemos definir o a propriedade nome do objeto carro. Por fim, ao encontrar a tag "/carro" devemos adicionar o objeto carro na lista.
Vamos agora a implementação da tela (lembrando que só estamos mostrando os métodos alterados):
#import "NGViewController.h"
#import "Carro.h"

@implementation NGViewController

- (void)viewDidLoad{
  [super viewDidLoad];
  self.title = @"Carros";
      carros = [[NSMutableArray alloc] init];
  // Pega o caminho do arquivo XML
  NSString *xmlPath = [[NSBundle mainBundle]
     pathForResource:@"carros" ofType:@"xml"];
  // Obtém os bytes do arquivo
  NSData *xmlData =
     [NSData dataWithContentsOfFile:xmlPath];
  // Cria o objeto que fará o parser do XML
  NSXMLParser *xmlParser =
     [[NSXMLParser alloc] initWithData:xmlData];
  [xmlParser setDelegate:self];
  [xmlParser parse];
}

// Métodos chamado quando NSXMLParser
// inicia a leitura de uma TAG
-(void)parser:(NSXMLParser *)parser
   didStartElement:(NSString *)elementName
   namespaceURI:(NSString *)namespaceURI
   qualifiedName:(NSString *)qName
   attributes:(NSDictionary *)attributeDict {

  currTag = elementName;      
  // Se achou a TAG carro, crie um novo objeto
  if ([currTag compare:@"carro"] == NSOrderedSame){
    carro = [[Carro alloc] init];  
  }
}

// Método chamado quando vai ler o 
// conteúdo da TAG
-(void)parser:(NSXMLParser *)parser
    foundCharacters:(NSString *)string {

  if ([currTag compare:@"nome"] ==
     NSOrderedSame && carro.nome == nil){
    carro.nome = string;
  } else if ([currTag compare:@"desc"] ==
     NSOrderedSame && carro.descricao == nil){
    carro.descricao = [string
       stringByTrimmingCharactersInSet:
        [NSCharacterSet
           whitespaceAndNewlineCharacterSet]];
  } else if ([currTag compare:@"url_info"] ==
     NSOrderedSame && carro.urlInfo == nil){
    carro.urlInfo = [string
       stringByTrimmingCharactersInSet:
        [NSCharacterSet
            whitespaceAndNewlineCharacterSet]];
  } else if ([currTag compare:@"url_foto"] ==
     NSOrderedSame && carro.urlFoto == nil){
    carro.urlFoto = [string
       stringByTrimmingCharactersInSet:
        [NSCharacterSet
           whitespaceAndNewlineCharacterSet]];
  }
}

// Método chamado quando termina de ler uma tag
-(void)parser:(NSXMLParser *)parser
   didEndElement:(NSString *)elementName
   namespaceURI:(NSString *)namespaceURI
   qualifiedName:(NSString *)qName {
  // Se a tag lida for "carro", adiciona à lista
  if ([elementName compare:@"carro"]==NSOrderedSame){
    [carros addObject:carro];
  }
}

// Método de UITableViewController
-(NSInteger)tableView:(UITableView *)tableView
   numberOfRowsInSection:(NSInteger)section {
  return [carros count];
}

-(NSInteger)numberOfSectionsInTableView:
  (UITableView *)tableView {
  return 1;
}

-(UITableViewCell *)tableView:(UITableView *)
  tableView cellForRowAtIndexPath:
   (NSIndexPath *)indexPath {
  static NSString *CellIdentifier = @"Cell";
  UITableViewCell *cell = [tableView
    dequeueReusableCellWithIdentifier:CellIdentifier];
  if (cell == nil) {
    cell = [[UITableViewCell alloc]
       initWithStyle:UITableViewCellStyleSubtitle
       reuseIdentifier:CellIdentifier];
  }
  Carro *_carro = [carros objectAtIndex:indexPath.row];
  cell.textLabel.text = _carro.nome;
  cell.detailTextLabel.text = _carro.urlInfo;
  return cell;
}

-(void)tableView:(UITableView *)tableView
   didDeselectRowAtIndexPath:(NSIndexPath *)indexPath {
  // Obtém o carro da linha selecionada
  Carro *_carro = [carros objectAtIndex:indexPath.row];
  // Abre o site do carro selecionado  
  NSURL *url = [NSURL URLWithString:_carro.urlInfo];
  [[UIApplication sharedApplication] openURL:url];
}
@end
O código acima está comentado e parte relacionada com a UITableViwController foi detalhada nesse post aqui.

Feito isso, precisamos fazer alguns ajustes no NIB. Abra o NGViewController.xib e remova a UIView e adicione uma UITableViewController. Ligue esse componente ao Outlet da propriedade view, e o dataSource e delegate.
Além disso, alterei também a propriedade "Top Bar" para "Navigation Bar". Feito isso, podemos rodar nossa aplicação. O resultado é apresentado abaixo.
Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

domingo, 3 de outubro de 2010

Conversão de código com ANTLR

Olá povo,

Estou no processo final do meu mestrado escrevendo minha dissertação e meu tema aborda a conversão automática de aplicações iPhone para Android. Devo admitir que uma das mais legais foi a conversão do código-fonte de Objective-C (do iPhone) para Java (do Android).
Para realizar essa etapa, utilizei o ANTLR (lê-se Antler), "um a sofisticado gerador de parser que pode ser utilizado para criar interpretadores de linguagens, compiladores e outros tradutores". Ele foi criado e é mantido por Terence Parr.

Essa ferramenta está disponível para download, inclusive com um plugin do Eclipse. O desenvolvedor especifica um arquivo de dicionário, que determina a sintaxe da linguagem especificada. A partir desse arquivo, é gerado o lexer que determina se as sentenças descritas em um arquivo de código-fonte estão em conformidade com a linguagem especificada. Feita essa validação sintática, o lexer passa a árvore com os tokens extraídos do arquivo para o parser. O ANTLR permite que sejam adicionados alguns trechos de código à gramática, tornando o parser gerado pela ferramenta, um tradutor.

Abaixo temos um exemplo da criação de uma linguagem chamada "T". Essa linguagem tem apenas uma regra chamada "r", que pode conter fantásticos DOIS comandos!!! :) Sendo assim, em um arquivo de código-fonte escrito em "T" podemos ter várias (indicada pelo sinal de "+" no arquivo de dicionário) instruções do tipo "print" e/ou "sum". A instrução print recebe um ID que foi definido como sendo qualquer sequência de letras maiúsculas ou minúsculas (número não são aceitos). Já a instrução sum recebe dois INT, que são definidos como qualquer sequência de números.
Notem que as instruções são terminadas com ";" e que temos o código Java para cada instrução, esse código é que será executado quando mandarmos executar um código que esteja de acordo com a linguagem "T".

grammar T;

// Regra r tem os comandos "print" | "sum"
r : ('print' ID ';' {
System.out.println("imprima "+$ID.text);}

| 'sum' id1=INT id2=INT {
int x = Integer.parseInt($id1.text);
int y = Integer.parseInt($id2.text);
int z = x + y;
System.out.println("soma "+ z) ; }
)+
;

ID: ('a'..'z' | 'A'..'Z') + ;

INT: '0'..'9' + ;

// Ignore espaços em branco
WS: (' ' |'\n' |'\r' )+ {$channel=HIDDEN;} ;

Uma vez criado esse dicionário, a ferramenta gera o parser e o lexer em Java. Podemos então finalmente screver nosso primeiro programa em "T" :)

import org.antlr.runtime.ANTLRStringStream;
import org.antlr.runtime.CommonTokenStream;

public class AmazingCompiler {
public static void main(String[] args)
throws Exception {

// Código fonte (poderia estar em um arquivo)
String codigoFonte = "print Glauber; sum 2 2;";

// criando stream com o código-fonte
ANTLRStringStream input =
new ANTLRStringStream(codigoFonte);

// Cria uma intância do Lexer que lê as instruções
TLexer lexer = new TLexer(input);

// Cria lista de tokens geradas a partir do lexer
CommonTokenStream tokens =
new CommonTokenStream(lexer);

// Cria o parser pra executar as instruções
TParser parser = new TParser(tokens);

// Manda executar a regra "r".
parser.r();
}
}


O código acima, passa o código-fonte em forma de um stream para o Lexer que o valida e gera os tokens que são repassados para o Parser. Esse último, por sua vez, manda excutar a regra "r", que como já foi dito, pode conter várias instruções "print" e "sum". Uma vez executado esse código, a saída no console será:

imprima Glauber
soma 4

Muito bacana! Agora você pode criar suas liguagens de programação e entrar no hall da fama :)

Tem muita coisa que podemos falar sobre essa fantástica ferramenta. Quem quiser mais informações sobre o ANTLR segue alguns links que foram de grande valia pra mim no meu mestrado.
- Site Oficial (http://www.antlr.org/)
- Vídeos explicativos sobre a ferramenta (http://javadude.com/articles/antlr3xtut/)
- Livro de Referência do ANTLR que é fonte desse artigo.

4br4ç05,
nglauber