Codemountain, Paulo Suzart's Blog

Scala, Traits, XML, XQuery e mais

with one comment

Seguindo o classico, vamos ao básico de XML com Scala.

O objetivo deste post é apresentar o básico do uso de XML em Scala, e aproveito o ensejo para falar sobre algumas outas características muito úteis da linguagem como traits e for comprehensions.

Scala integra na linguagem á syntax XML, ou seja, para construirmos um elemento XML conforme listagem abaixo, não é preciso mais que atribuir este mesmo bloco XML a uma variável:

<person>
     <name>Caro</name>
     <age>21</age>
</person>


val person =
<person>
           <name>Caro</name>
           <age>21</age>
      </person>

Este código atribui um scala.xml.Elem à variável person. Mas, muito comumente a intenção é produzir um xml a partir de um objeto.

Considere a classe Person:


class Person(n: String, a: Int) {
   val name = n; val age = a
   def this(n: String) = this(n, 18)
}

Aqui, vamos aproveitar e observar o “;” no código. Neste caso, existem duas atribuições  numa única linha, e por val age = a ser uma atribuição, esta deve ser separada da atribuição anterior. Scala define algumas regras pra o uso, ou não, de ponto e vírgula(;).

A classe Person define 2 parâmetros, n e a, que representam os valores para o nome e a idade de Person. Parâmetros por que Scala considera uma classe parametrizável desse jeito. Os valores de n e a estão disponíveis no scopo da classe, mas como iremos acessar estes valores externamente, atribuimos os parâmetros n e a para dois atributos públicos: name e age, de tipos String e Int respectivamente, devidamente inferidos pelo compilador a partir dos tipos de n e a. Esse pequeno detalhe já deixa o código mais limpo e menos repetitivo.

E como sabia que a maioria vai se pensar : e se quisermos um construtor que só recebe o nome, e para a idade um padrão seja assumido?

A resposta está na linha 3. Esta é a notação de scala para um construtor que recebe um único parâmetro. Novamente utilizo this para invocar o “construtor padrão”, ou seja, a inicialização da classe com os valores n e 18.

Agora, pra tornar as coisas mais interessantes, vamos definir uma trait que possui duas funções:  xml e toString.

//importe do pacote xml.
//em java seria import scala.xml.*
import scala.xml._
//nossa trait
trait Xmlable {  

	def xml : NodeSeq 

	override def toString () = new PrettyPrinter(80,5).formatNodes(xml)

} 

//instanciando um Person com a trait
val p = new Person(n,a) with Xmlable {
    def xml =
<person><name>{name}</name><age>{age}</age></person> } 

println (p) /* imprime o resultado:
<person><name>Caro</name><age>21</age></person> */

Uma trait é um misto de Inferface e Abstract Class, pois nela definimos uma função abstrata (xml) e sobrescrevemos a função toString, que assim como em Java, todo objeto em Scala possui. Esta função é definida em AnyRef. Uma trait também possibilita um comportamento semelhar a herança múltipla, pois podemos tranquiliamente instanciar um Person with Xmlable with OtherTrait with EvenAnotherOneTrait, etc.

A função toString faz o uso da classe utilitária PrettyPrinter para formatar o xml.

A intenção é não embarcar estaticamente na classe alvo (Person), o comportamento de se transformar em xml. Logo, ao instanciarmos uma pessoa, informamos que aquele objeto será um Xmlable. A partir deste ponto o objeto Person passa a ter um comportamento de Person e de Xmlable, mesmo que nossa hierarquia inicial não tenha indicado Person como descendente de Xmlable.

A função xml da trait é como um método abstrato numa classe ou interface em java. A diferença é que, em Scala, se não informamos a implementação para um método numa trait, o compilador infere que aquilo é um método abstrato e deve ser implementado na subclasse, sem que tenhamos que indicar modificadores do tipo abstract na classe ou no método. Nossa implementação do método xml fica no ato de instanciar Person with Xmlable.

Ainda na listagem anterior, invocamos uma função xml em person que nos dá o resultado de um Person convertido em xml. Simples?

Bom, ao  instanciarmos um Person, não seria uma boa ideia informar o xml toda vez. Scala fornece um tipo especial de objeto, que é um singleton por natureza. E quando usamos um singleton em conjunto com a classe (utilizando inclusive o mesmo nome), estamos fazendo o uso de Companion Objects.

Com esta técnica podemos criar uma espécie de factory que servirá de atalho para instanciar um Person Xmlable:


object Person {
   def xmlPerson(n: String, a: Int)  = new Person(n,a) with
	Xmlable {
		def xml =
<person><name>{name}</name><age>{age}</age></person>
	}  }
//importamos a função
import Person.xmlPerson

val p = xmlPerson("Caro", 21) //atalho.
println(p.xml) //produz o xml de person.

Scala possui bibliotecas que funcionam de modo semelhante ao jax-b do Java. Mas a intenção aqui é mostrar as habilidades da linguágem no uso de XML.

Agora que sabemos produzir um XML de person a partir de um objeto Person, vamos ensaiar produzir um objeto Person a partir de um XML de Person. Para isso vamos definir uma nova função no object Person. Esta função retornará um Array de Persons, pois ela pode receber mais de um elemento do tipo <person>. Vamos ao código:

object Person {  
   def xmlPerson(n: String, a: Int)  = new Person(n,a) with 
      Xmlable {  
           def xml =
<person><name>{name}</name><age>{age}</age></person>  
      }

   def fromXml(xml: NodeSeq) = {
      for { p <- xml \\ "person"
             name <- p \ "name"
             age <- p \"age"
             nameText = name.text
             ageText = Integer.parseInt(age.text) } yield xmlPerson(nameText, ageText)
    }

}
//instanciando uma pessoa a partir de um xml
val persons = fromXml (person)
println(persons(0).name)
println(persons(0).age)

//documento com muitos
<person>
val document =
<persons>
<person><name>Paulo</name><age>26</age></person>
<person><name>Caro</name><age>18</age></person>
	  </persons>
val persons2 = fromXml (document)

println(persons2(1).name)

A função fromXml recebe uma sequencia de nós <person> (definida no início do post) e executa um código com o mesmo poder de uma  XQuery, com a diferença da notação “/” e “//” do XQuery ser substituída por métodos cujo nome são “\” e “\\”. Essa consulta extrai os valores para o nome e idade, devolvendo uma lista de objetos Person.

A lista que este método retorna vem do for, que em Scala pode retornar valor, usando o formato for (x <- e) yield e’. Assim, podemos fazer consultas não só em XML, mas uma lista de objetos, por exemplo.
Este código ainda exibe um exemplo onde um documento cujo elemento raiz <persons> engloba dois elementos <person>. O resultado é uma lista de Person.

Falando em XQuery, podemos consultar este document sem a necessidade de instanciar um objeto do tipo Person. O código abaixo mostra duas consultas, a primeira buscando Person com idade superior a 18 e a segunda devolvendo a média da idade das Persons contidas no documento.

//pessoas maiores de 18
for { p <- document \\ "person"
        name <- p \ "name"
	age <- p \"age"
	nameText = name.text
	if Integer.parseInt(age.text) > 18} println(nameText) // Paulo

//media das idades
val ages = for { a <- document \\ "age" } yield Integer.parseInt(a.text)
println(ages.reduceLeft ((x : Int, y : Int) => x+y) / ages.length) // 22

A habilidade de trabalhar diretamente no código com XML, permite uma flexibilidade que não conseguimos tão naturalmente em Java, por exemplo. Você pode encontrar um comparativo aqui.

Existem situações onde esse tipo de recurso é muito vantajoso, imagine serviços REST, scripts de dumping, backup, build, servlets, integração, etc. O Lift se vale desta possibilidade para construir um Framework MVC Web muito poderoso.

Obs: Os exemplos expostos aqui poderiam ter sido construído de outras formas. São apenas exemplos para forçar a demonstração de algumas features.

Todo o script usado neste post você encontra aqui.

Written by paulosuzart

março 1, 2009 às 1:38 am

Publicado em scala, xml

Uma resposta

Subscribe to comments with RSS.

  1. […] 12, 2009 in Java, scala | by paulosuzart Post curto outra vez enquanto nos recuperarmos de um outro mais longo que fiz duas semanas […]


Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

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

%d blogueiros gostam disto: