editorial
índice
3
4
notícias
tema de capa
- Estado de Visualização em
ASP.NET
7
a programar
- Lucene: Programar um .
motor de busca
- Programas Circulares
- Serialização e
.
Desserializaçao de Objectos
em C#
- Manipulação de Datas
19 segurança
- Internet Protocol Version 6
23 análise
24 eventos
27 internet
equipa PROGRAMAR
coordenador
Miguel Pais
coordenador adjunto
Joel Ramos
editor
Pedro Abreu
redacção
O futuro (da pesquisa...)
Quando se fala em pesquisa
decerto todos reconhecem o
nome Google quase ao ponto
de sinónimo, não apenas de
pesquisa, mas mais importante
que isso, de pesquisa com
sucesso. Ao longo dos anos o
Google tem sabido manter-se
na ribalta do acesso ao
conhecimento em todo o
mundo, enveredando por outros
projectos mas dando sempre a
Coordenador Adjunto
desde a 4ª edição, é
actualmente
o
Coordenador
da
Revista Programar.
Frequenta o curso de
Engenharia Informática
e de Computadores, no
IST.
Miguel Pais
devida atenção à inovação (que os marcou desde o inicio) no seu produto principal.
Menos conhecido, menos acedido e utilizado em terras lusas, mas igualmente popular a
nível mundial encontra-se o Digg, representando o conceito de website social relativo a
notícias. O conceito é trivial mas a ideia inovadora para a altura: notícias são submetidas
por utilizadores sendo promovidas por votos positivos destes.
O que têm em comum estes dois gigantes do seu ramo? Bem, nos últimos dias (segunda
semana de Julho) foi reportada peloTechCruch uma nova “feature” do Google que
parece agregar o conceito do Digg às pesquisas de utilizadores, ou seja, cada um tem a
possibilidade de enviar um resultado do Google para os confins da numeração de
páginas, ou trazer outro para o topo. Além disso há a possibilidade de comentar os
websites e os seus respectivos comentários, com um sistema de pontuação bastante
semelhante ao do Digg. Com isto qualquer um poderá pôr o resultado que realmente lhe
interessa como primeiro a ser retornado pelo Google para aquelas keywords, o que é
algo que faz bastante sentido. A maneira como esta componente social influenciará num
todo no futuro os resultados do Google será algo interessante de analisar.
Augusto Manzano
Ciro Cardoso
João Rodrigues
Miguel Bento
Miguel Alho
Vitor Tomaz
Posto isto algo desde logo salta à vista: tudo o que antes recebíamos como passivo está a
evoluir para algo activo. O tempo em que o utilizador não tinha nada a dizer na
informação que recebia acabou. Já ninguém quer receber passivamente informação sem
ter voto na matéria, os utilizadores querem a escolha e a decisão. E tudo isto faz sentido.
Toda a Internet já percebeu isso, mas seria interessante que não só este meio virtual
aderisse a este estilo.
colaboradores
A televisão começa a ser menosprezada pela Internet, onde cada um vê o que quer às
horas que quer e a televisão não ganhará de novo a batalha se não se converter ao
mesmo género. As mais recentes tentativas na área da televisão em Portugal são boas,
mas ainda não representam o estado real em que os utilizadores se encontram. A
verdadeira televisão do futuro pode mostrar ao utilizador sempre que ele queira um
programa da semana passada, instantaneamente e sem a construção de enfadonhos
planos de gravação agendada. A própria rádio usual já não faz sentido. Decerto
utilizadores da Internet habituados a ouvirem apenas o que gostam em rádios de
serviços como o Last.fm deverão já achar ridículo alguém se submeter a ouvir algo que
pode muito bem não corresponder em nada aos seus gostos. Seria importante que muita
gente olhasse para a web e começasse a aplicar o que lá vê às mais pequenas coisas da
nossa sociedade, a nossa maneira de receber e lidar com a informação seria bem melhor.
José Fontainhas
David Ferreira
Pedro Teixeira
contacto
revistaprogramar
@portugal-a-programar.org
website
www.revista-programar.info
Veremos o que o futuro nos reserva…
1) Referência: http://www.techcrunch.com/2008/07/16/is-this-the-future-of-search/
2) Uma nota para a mudança dos meses de publicação da revista, estes são agora os
meses pares. Esperamos que a decisão não tenha prejudicado ninguém e que continuem
a conf iar neste projecto.
<2>
notícias
Wii é a nova líder de mercado nos EUA
A Wii já é a consola mais vendida nos Estados Unidos. Só em Junho foram comercializados 666 mil equipamentos, o que
fixou o número total de dispositivos vendidos, desde Novembro de 2006, altura em que saiu para o mercado, nos 10,9
milhões de consolas.
Os resultados foram recolhidos pelo NPD Group que concluiu na análise ao mercado referindo que o equipamento da
Nintendo já destronou a Xbox 360 da Microsoft, lançada um ano antes.
Como em todos os segmentos de mercado, também neste as opiniões divergem, com cada fabricante a defender o seu
posicionamento no mercado. Todas afirmaram na Electronic Entertainment Expo (E3) que são as mais populares embora
os números dêem a liderança das vendas à Nintendo.
A Wii foi concebida a pensar num tipo de divertimento diferente das consolas que já existiam no mercado. Introduziu uma
nova forma de jogar graças aos comandos sem fios e jogos que apelam ao movimento do utilizador. A estratégia da
Nintendo foi bem recebida pelo público de todas as idades, em todo o mundo, acabando por se revelar um caso de
sucesso.
Os números não enganam e mostram o destaque que a consola alcançou no mercado norte-americano, onde as
concorrentes PS3 e Xbox 360 venderam respectivamente 405,5 e 219,8 mil equipamentos.
As vendas totais no mesmo mercado subiram 53 por cento face a Junho do ano passado, colocando o valor da indústria nos
1,69 mil milhões de dólares. Já as vendas de jogos subiram 61 por cento, fixando-se agora nos 872,6 milhões de dólares.
SAPO Summerbits
Apple torna-se o terceiro maior fornecedor de
computadores
A Apple tornou-se o terceiro maior fornecedor de
computadores pessoais nos EUA, ultrapassando a Acer,
de acordo com uma pesquisa da Gartner, que adiante um
crescimento de 38,1% em vendas, no segundo trimestre
de 2008.
Já arrancou o SAPO Summerbits, um programa
promovido pela empresa do Grupo PT em parceria com a
Associação Ensino Livre que tem como objectivo
promover bolsas para estudantes que desenvolvam
código para projectos de software livre.
O mercado de PC´s nos EUA apresentou um crescimento
médio de 4,2%, no último trimestre, com a venda de 16,5
milhões de máquinas. Só a empresa de Steve Jobs
comercializou 1,4 milhões de desktops naquele período,
enquanto a Acer registou 1,33 milhões de vendas.
Os incentivos destinam-se a alunos que tenham mais de
18 anos e vínculos a qualquer grau de ensino ou tipo de
escola, desde que nacional. No final, as 10 ideias que mais
se destaquem, quer sigam linhas de tecnologias
existentes ou sejam completamente novas, serão
financiadas em 2.500 euros.
A Dell, que lidera o segmento, vendeu 2,5 milhões de
unidades, seguida pela HP. Apesar do terceiro lugar, a
Apple ainda tem uma participação tímida no mercado
mundial de computadores, o que pode representar uma
oportunidade de crescimento.
A meta do SAPO Summerbits é atingir um patamar de
referência entre as iniciativas académicas e junto das
comunidades de software livre do país. Os interessados
em participar no programa deverão inscrever-se até 19
de Julho deste ano na página oficial do evento.
No Japão, por exemplo, a marca vendeu 130 mil
máquinas no primeiro trimestre, de acordo com a IDC.
<3>
tema de capa
semelhante a uma tabela de hash mas tem a capacidade de
registar alterações (fazer tracking), ou seja, permite que
sempre que um valor seja alterado na colecção seja também
marcado como “dirty”.
Estado de Visualização
em ASP.NET
Antes de entrar em pormenores sobre estado de
visualização é necessário compreender o que é um controlo
e como este funciona em ASP.NET. De certa forma o
ASP.NET é uma arquitectura baseada em controlos, já que
uma página é um controlo e qualquer controlo pode conter
controlos filhos.
A arquitectura é semelhante à arquitectura de janelas do
Windows, onde o próprio ambiente de trabalho é uma
janela, que pode conter janelas filhas. Cada janela é
apresentada, apresentando primeiro o seu conteúdo e
depois apresentando o conteúdo das janelas filhas. O
mesmo se passa em ASP.NET em que cada controlo é
apresentado, apresentando primeiro o seu conteúdo e
depois apresentando o conteúdo dos seus filhos. A
apresentação de uma janela em Windows envolve o
desenho de pixeis no ecrã, enquanto que a apresentação de
um controlo ASP.NET envolve a geração de HTML para
preencher uma parte da resposta a um pedido HTTP.
Uma página serve como controlo raiz e tem três controlos
filhos imediatos: um controlo literal para gerar o texto de
início da página, um controlo do lado do servidor HtmlForm
para representar o formulário e todos os seus controlos
filho, e por fim outro controlo literal para gerar o texto de
fim da página. Todos os controlos adicionados a uma página
estarão dentro do formulário e portanto serão filhos do
HtmlForm, em que a ordem pela qual estão definidos dentro
desse controlo será a ordem pela qual serão apresentados.
Cada um destes controlos tem o seu próprio estado de
visualização. Todos os controlos que correspondem a
elementos de formulário têm a sua manutenção de estado
suportada através do envio automático do valor dos
elementos quando o formulário é submetido (post back).
Todos os outros terão que definir o seu mecanismo de
persistência de estado e será sobre este assunto que iremos
conversar.
Em ASP.NET, o estado de visualização entre post backs é
mantido através de uma colecção de pares nome/valor
acessíveis por qualquer controlo a partir da propriedade
ViewState. Quase todo o estado do controlo, se não todo,
fica guardado nesta colecção. Esta propriedade retorna uma
instância do tipo System.Web.UI.StateBag, que é muito
A função de registo de alterações pode estar ligada ou
desligada, mas uma vez ligada não pode ser desligada. De
modo a activar a função use o método TrackViewState(). Se
a função de tracking estiver ligada qualquer alteração a um
objecto fará com que esse objecto fique marcado como
“dirty”. Podemos consultar se o objecto está marcado ou
não através do método IsItemDirty(string chave) ou forçar a
marcação através do método SetItemDirty(string chave). O
leitor tenha em atenção um pormenor, após a activação de
TrackViewState() qualquer alteração será marcada, mesmo
que o objecto seja alterado para o mesmo estado, como por
exemplo:
stateBag["nome"] = "valor";
stateBag.IsItemDirty( "nome"); // falso
stateBag.TrackViewState();
stateBag["nome"] = "valor";
stateBag.IsItemDirty( "nome"); //
verdadeiro
A colecção ViewState, como já foi referido, guarda pares
nome/valor, os pares podem ser indexados por string e ter
qualquer object como valor. Exemplo:
ViewState["ViewStateVariableName" ] =
1;
Para ter acesso à variável guardada basta fazer a indexação
com a chave e a respectiva conversão.
int number = (int)
ViewState["ViewStateVariable"];
Esta colecção também permite guardar os nossos próprios
tipos quase tão facilmente como os tipos básicos. Para tal
basta apenas o tipo ser serializável, ou seja, consegue-se
converter uma instância desse tipo para uma sequência de
bytes e posteriormente fazer a sua recuperação. Vai
compreender a necessidade de o tipo ser serializável mais á
frente neste artigo quando falarmos de um campo de input
oculto chamado __VIEWSTATE.
<4>
tema de capa
valor padrão, caso contrário, retornar o valor obtido.
[Serializable]
public class Pessoa
{
public string _nome;
public int _idade;
public string Texto
{
get { return ViewState["Texto"] ==
null ?
"Valor por Defeito" :
(string)ViewState["Text"];
}
set { ViewState["Texto"] = value;
}
}
public Pessoa(string nome, int
idade)
{
_nome = nome;
_idade = idade;
}
}
Como a classe Pessoa está marcada como serializável pode
ser guardada em ViewState:
Pessoa p = new Pessoa( "Vitor", 25);
ViewState["Cliente"] = p;
Lembre-se que terá de efectuar a respectiva conversão
quando necessitar de obter o valor guardado.
Pessoa p = (Pessoa)
ViewState["Cliente"];
O protocolo HTTP é stateless, ou seja, cada pedido é
executado independentemente e sem conhecimento de
pedidos anteriores. Portanto cada pedido feito à
arquitectura ASP.NET será servido por uma instância
diferente do controlo e por isso não é possível guardar
estado de visualização entre post backs em campos de
instância. Devido a isso as propriedades em ASP.NET terão
um aspecto diferente já que deverão usar a colecção
ViewState para guardar qualquer valor:
Repare que ao afectar com null uma propriedade definida
com este padrão essa propriedade passará a retornar o valor
por defeito e não null como acontece com as propriedades
ditas normais. Uma alternativa poderá ser afectar a
propriedade com String.Empty (um campo que representa
um string vazia) em vez de null. De salientar ainda que cada
controlo pode aceder á sua colecção ViewState em qualquer
momento e por qualquer razão, não apenas através de
propriedades.
Deve-se utilizar esta colecção quando as propriedades
reflectem directamente tipos primitivos. No caso de
controlos que têm pretendem manter um estado mais
complexo, usando tipos próprios poderá ser mais
complicado utilizar a colecção ViewState. Como alternativa
podemos sobrepor dois métodos virtuais definidos na classe
base Control sendo eles o SaveViewState() e o
LoadViewState(). Estes métodos permitem escrever e ler
manualmente o estado do controlo a partir da stream
ViewState. Para um objecto poder ser guardado nessa
stream tem que ser serializável. O método SaveViewState(),
como o nome indica, permite guardar o estado de
visualização. Note que este método também é responsável
por chamar o método da base e guardar, no objecto a
retornar, o resultado dessa chamada.
protected override object
SaveViewState()
{
ArrayList OsMeusDados = new
ArrayList();
object[] vState = new object[2];
vState[0] = base.SaveViewState();
vState[1] = OsMeusDados;
return vState;
}
public int ValorInteiro
{
get { return
(int)ViewState["ValorInteiro"]; }
set { ViewState["ValorInteiro"] =
value; }
}
Nos casos em que é necessário ter um valor por defeito
teremos também que ter em conta que estamos a utilizar a
colecção ViewState. Tal como uma tabela de hash, uma
StateBag irá retornar null se a colecção não contém uma
entrada com essa chave. Portanto, se o valor retornado for
nulo é porque ainda não foi atribuído, então deve retornar o
O método LoadViewState() permite recuperar o estado de
visualização. Note que este método também é responsável
por chamar o método da base, passando ao mesmo os
dados que lhe pertencem.
<5>
tema de capa
protected override void
LoadViewState(object savedState)
{
if (savedState != null)
{
ArrayList OsMeusDados;
// Obter o array de objectos
guardados em SaveViewState
object[] vState =
(object[])savedState;
correspondem a controlos que implementam
IPostBackDataHandler e de seguida é invocado o método
LoadPostData para todos esses controlos. A string
postDataKey, passado como argumento, contém o
identificador único associado ao controlo, que pode ser
usado para indexar sobre postCollection para localizar o
valor corrente do controlo dentro da colecção como se pode
verificar no seguinte exemplo:
if (vState[0] != null)
base.LoadViewState(vState[ 0]);
if (vState[1] != null)
OsMeusDados =
(ArrayList)vState[1];
}
}
O leitor certamente reconhece um padrão de recursividade
na chamada a estes métodos e é desta forma que a
arquitectura ASP.NET constrói uma estrutura de dados com
todo o conteúdo de estado de visualização. Como já foi
referenciado, o protocolo HTTP “não tem memória”. Isso
significa que o estado da página terá que ser guardado e
posteriormente reposto no pedido seguinte. Os pares
nome/valor colocados na colecção ViewState antes da
apresentação da página são armazenados num campo de
input oculto, __VIEWSTATE, e quando a página é de novo
acedida através de um pedido de POST, o conteúdo do
campo __VIEWSTATE é analisado e usado para reconstituir
a colecção ViewState.
Falta-nos agora perceber como é mantido o estado dos
controlos de formulário. Como já foi dito anteriormente,
todos os controlos que correspondem a elementos de
formulário têm a sua manutenção de estado suportada
através do envio automático do valor dos elementos quando
o formulário é submetido (post back). Esta manutenção de
estado é realizada através da implementação da interface
IPostBackDataHandler.
public interface IPostBackDataHandler
{
bool LoadPostBackData(string
postDataKey, NameValueCollection
postCollection);
void RaisePostDataChangedEvent();
}
Neste caso a arquitectura faz uma pesquisa no conteúdo do
POST de modo a determinar se existem entradas que
public virtual bool
LoadPostData(string postDataKey,
NameValueCollection postCollection) {
string valorAntigo = Text;
string valorNoPOST =
postCollection[postDataKey];
if
(!valorAntigo.Equals(valorNoPOST)){
Text = valorNoPOST;
return true;
}
return false;
}
O resultado deste método deve ser true se mudou o valor do
estado do controlo, caso contrário, o método deve devolver
false. Para todos os controlos que retornam true neste
método é chamado o RaisePostDataChangedEvent de cada
um desses controlos de modo a poderem ser desencadeados
eventos de alteração de estado. O leitor repare que o
carregamento de dados de POST ocorre em duas fases, o
método LoadPostData ocorre antes do método de Load da
página e o método de RaisePostDataChangedEvent ocorre
depois do método de Load. Isto permite que na altura em
que as notificações são geradas todos os controlos tenham o
seu estado reposto.
Note ainda que, para além dos eventos de alteração de
estado (eventos changed), como por exemplo alterações do
texto de uma caixa de texto ou a alteração do índice
seleccionado de uma DropDownList, também os eventos de
reacção utilizam este mecanismo. Por exemplo, o evento de
click de um botão é detectado e lançado utilizando esta
interface. Como se deve ter apercebido, a manutenção de
estado de controlos de formulário não usa o campo oculto
__VIEWSTATE em nenhuma das suas fases e é
<6>
tema de capa
completamente independente deste. É por essa razão que,
mesmo que o ViewState não esteja activo, todos os
controlos de formulário têm o seu estado automaticamente
reposto.
No entanto, na fase LoadPostData, é necessário saber o
valor antigo de modo a poder compará-lo com o que vem no
POST. Uma das formas é usar o ViewState para
previamente guardar esse valor, que depois será usado na
comparação com o valor recebido no POST. Note que,
apesar da sua ligação próxima, são funcionalidades
independentes.
Voltando ao ViewState, a estrutura de dados com todo o
conteúdo de estado de visualização não é directamente
convertida para string e armazenada no campo
__VIEWSTATE. Lembra-se de falarmos que uma StateBag
permitia fazer tracking? Ora bem, apenas são salvos no
campo oculto as entradas da StateBag que estão marcadas
como “dirty”, ou seja, apenas as entradas em que os seus
estados são diferentes do estado estático ou estado por
defeito. Não faz sentido guardar estado que será reposto
automaticamente assim que exista um novo pedido e seja
criada uma nova instância do controlo para atender esse
pedido. Neste momento o leitor poderá estar a perguntar-se
“então e os controlos definidos declarativamente?”
Podemos definir declarativamente um controlo, como por
exemplo:
chamado no evento Init do ciclo de vida de uma página e
todas estas afectações são feitas antes desse momento, ou
seja, não serão consideradas “dirty”.
Como referido, quando o ocorre a fase de Init é chamado o
TrackViewState(). Após o TrackViewState() ser chamado
para todas as StateBags é chamado o LoadViewState(), que
faz com que todo o estado dinâmico de visualização
guardado seja reposto e, mais importante ainda, visto que
neste momento o tracking já está activo todo o estado
dinâmico carregado será considerado “dirty”. Esta sequência
fará com que o estado dinâmico de visualização seja reposto
e fique novamente persistente para futuros post backs, ou
seja, faz com que seja novamente seriado para
__VIEWSTATE. Através deste artigo espero que o leitor
fique com uma ideia mais clara de como manter estado nos
seus controlos tendo em atenção que quase todo, se não
todo, o estado de visualização de um controlo é guardado
em ViewState, mas apenas é persistido entre post backs o
estado dinâmico. O estado por omissão que é criado quando
é criada uma instância desse controlo (estado estático) não
é persistido.
Bibliografia
Essential ASP.NET with Examples in C# - Fritz Onion Addison Wesley
Understanding
ASP.NET
View
State
http://msdn.microsoft.com/en-us/library/ms972976.aspx
<asp:Label ID="Label1" runat="server"
Text="Label">
No momento em que a arquitectura ASP.NET faz parse do
formulário, ao encontrar o atributo runat=”server” cria uma
instância desse controlo e tenta corresponder todos os
atributos definidos a propriedades publicas do controlo. No
exemplo anterior olhando para o atributo Text percebemos
que a arquitectura vai afectar a propriedade pública Text
com o valor “Label”. Sabendo nós que quase todo o estado
do controlo, se não todo, fica guardado em ViewState
poderíamos ser levados a pensar que estes dados seriam
considerados “dirty” visto que estamos a mudar o seu
estado. Tal não acontece porque o TrackViewState() só é
-
ViewState
in
ASP.NET
2.0
http://www.beansoftware.com/ASP.NETTutorials/ViewState-In-ASP.NET.aspx
TRULY
Understanding
ViewState
http://weblogs.asp.net/infinitiesloop/
archive/2006/08/03/Truly-Understanding-Viewstate.aspx
IPostBackDataHandler
Interface
h t t p : / / m s d n . m i c r o s o f t . c o m / e n us/library/system.web.ui.ipostbackdatahandler.aspx
StateBag Class - http://msdn.microsoft.com/enus/library/system.web.ui.statebag.aspx
Vitor Tomaz é estudante de Engenharia Informática e
Computadores no ISEL bem como Trabalhador
Independente na área de Tecnologias de Informação.
Tem especial interesse por Programação Concorrente,
Segurança Informática e Aplicações Web.
Vítor Tomaz
<7>
a programar
altamente ordenada de texto, ou de pedaços de texto
(tokens), que torna a pesquisa muito mais eficiente. Porém,
falha na capacidade de compressão de dados. Um índice de
6 milhões de abstracts científicos atinge facilmente os 1.7
GB, no caso de armazenar apenas tokens, ou os 6.6 GB caso
guarde o texto. Esta diferença de tamanho vai, obviamente,
afectar a performance. Daí, uma vantagem para a pesquisa,
é ter o índice fragmentado em vários “sub-índices”, de modo
a cada um ter menos volume de informação.
Lucene: programar um
motor de busca
O que é o Lucene
O Lucene talvez dever-se-ia tratar por “a” Lucene, uma vez
que se trata de uma biblioteca de recuperação de
informação textual (do inglês “information retrieval”) de
código aberto e criada por Doug Cutting. Originalmente, foi
escrita em Java, mas foi rapidamente adaptada a outras
linguagens de programação, nomeadamente Python
(Pylucene), Perl (Plucene), C# (Lucene.net), C++ (CLucene),
e Ruby (Ferret). Contudo, estas adaptações estão
normalmente ligeiramente atrasadas no que toca à versão
original em Java, actualmente mantida e alojada pela
Apache Sofware Foundation.
Simples de aprender a usar, mas poderosa nas mãos de um
programador experiente, esta biblioteca suporta desde
índices estáticos com um campo, até múltiplos índices em
paralelo, com centenas de campos e milhares de acessos
simultâneos. É ideal para todo o tipo de projectos, desde o
simples website com a “search box” até um grande motor de
busca sobre, por exemplo, a colecção de PDFs que se tem no
disco rígido.
Onde é usado o Lucene
Provavelmente desconhecido para o leitor, o certo é que já
passou os dedos por esta útil ferramenta. Ao fim de contas,
quem nunca pesquisou no site Sourceforge, ou na CNET, ou
mesmo até na Wikipedia? Para os utilizadores de Ubuntu,
quem nunca tirou partido do software de indexação
recentemente incorporado na distribuição: o Beagle. Este,
não é mais que suportado pela Lucene.Net, a
implementação em C# desta biblioteca. Assim, a utilização
desta biblioteca por estas entidades (re)afirma por si só a
qualidade e a utilidade da mesma.
Como funciona o Lucene
O modo de funcionamento do Lucene é trivial de se
perceber, mas mais complexo de se manusear na perfeição.
Para começar, como já se indicou, há uma palavra chave em
todo o processo: índice. O índice não passa de uma estrutura
O modo como o texto é tratado pelo Lucene, também é alvo
de atenção. Ao lermos texto para o lucene, ele será primeiro
submetido a um processo chamado analisador (analyzer),
que pode ter várias formas: Standard, Simple, Stop,
regional, etc. Sem entrar em grandes explicações, cada
analisador trata o texto de maneira diferente. Uns ignoram a
pontuação, outros são específicos para certas línguas (como
a nossa com o til e os acentos), outros ignoram palavras
comuns, baseando-se numa lista de “stop-words”, etc.
De seguida, tem que se criar um índice, que pode ser, na
maior parte das vezes, ou baseado no disco rígido (acesso
mais lento), ou na memória RAM (temporário). Uma
estratégia para aumentar a velocidade de construção do
índice passa por criar o índice na RAM e no fim, “despejá-lo”
para o disco rígido. Na pesquisa, o mesmo índice pode voltar
a ser lido para a RAM, potenciando (muito
significativamente) a performance. Tendo o índice criado,
há que criar um escritor de índices, de modo a populá-lo
com os nossos textos. De entre as várias opções a passar ao
escritor, estão o analisador em causa e o índice previamente
criado.
A inserção de documentos no índice faz-se por último,
adicionando documento a documento, criando os campos
que forem precisos (por exemplo, a data de criação de um
ficheiro, e o seu conteúdo, ou o título de um livro, a sua
editora, autor, ano de lançamento, e preço). Quantos mais
campos, mais lento ficará o índice, mas mais específica
poderá ser a nossa pesquisa. Tomando o exemplo da livraria,
podemos pesquisar apenas por editora, ou por ano de
lançamento, ou mesmo por preço. A maneira de guardar a
informação também varia. Podemos ter a necessidade de
guardar alguns campos para mais tarde devolver ao
utilizador que pesquise, mas outros campos podem ser
fúteis e são apenas guardados “tokens” dos mesmos. Estes
tokens são fragmentos únicos do texto e não estão
disponíveis para futuras recuperações. Exemplificando, um
resumo de um artigo científico está dividido em título e
resumo. Podemos pesquisar sobre os resumos por uma
determinada palavra, sendo devolvidos os títulos que
correpondem à nossa pesquisa.
No fim do índice criado, pode-se, opcionalmente, mas
sempre recomendado, passar por um processo de
optimização que vai aumentar a performance da pesquisa
<8>
a programar
no índice recém criado. Este processo vai reduzir os
“segmentos” do índice a apenas um, facilitando assim a
tarefa de abrir o índice, em vez de múltiplos pequenos
ficheiros. Explicações sobre a formação destes segmentos
estão para além do âmbito deste artigo, bastando por agora
dizer apenas que são resultado da construção do índice,
podendo ser imaginados como sub-índices que degradam
significativamente a performance devido ao seu diminuto
tamanho e conteúdo.
Após se ter um índice, resta programar o pesquisador. O
pesquisador vai usar um pesquisador de índices
(IndexSearcher), que vai precisar da directoria do índice (tal
como o IndexWriter), e de um parser da pesquisa
(QueryParser), que vai depender do nome do campo onde
pesquisar e de um analisador (igual ao usado na construção
do índice). Recuperam-se depois os resultados, que estão
num iterador, e depois vai-se retirar de cada objectoresultado o campo pretendido (voltando ao exemplo do
artigo científico, o título do artigo). Os resultados estão
ordenados por ordem de relevância. O próprio Lucene tem
um mecanismo de atribuição de relevância, baseado no TFIDF, que dá mais peso a documentos com maior frequência
do termo, e a termos pouco comuns no geral.
Desta forma, é simples construir um motor de busca, desde
que se tenha maneira de ler os ficheiros (parser de PDFs
para PDFs por exemplo). A documentação oficial do
projecto é bastante completa e simples de seguir,
fornecendo um caminho perfeito para tirar as dúvidas que
possam surgir. Para além disso, há bastantes mailing-lists
que ajudam os novatos. A cereja no topo do bolo, é a
inclusão de vários scripts exemplo na distribuição do
Lucene, e do PyLucene por exemplo. Estes exemplos são um
óptimo ponto de partida para o Lucene.
PyLucene: usando o Lucene em Python
A biblioteca PyLucene está disponível desde, pelo menos,
2004. Criada e mantida por uma única pessoa, Andi Vadja,
veio não só trazer aos utilizadores de Python o poder do
Lucene, como também o fez de uma forma extremamente
simplista e fiel à biblioteca original. De início, havia duas
implementações: GCJ e JCC. Porém, desde a versão 2.3.0 do
Lucene, a implementação em GCJ foi deixada de parte. As
principais diferenças entre as implementações traduziam-se
na velocidade de pesquisa, onde a GCJ liderava por larga
margem, velocidade de indexação, onde era a JCC a levar a
melhor, e noutras características como o tamanho dos
índices suportados (maior na JCC) e maior estabilidade
(JCC). Contudo, o processo de instalação tornou-se mais
complicado, daí que apesar das fontes serem cedidas para
compilação pelo utilizador, há quem disponibilize versões
binárias para os mais novatos.
Exemplos de código
Tal como qualquer biblioteca em Python, chama-se o
Lucene da seguinte forma:
import lucene
Porém, isto não chega. Com a implementação JCC,
trabalha-se dentro de uma máquina virtual de Java, sendo
portanto necessário criá-la, podendo definir-se os seus
parâmetros (maxheap - limite máximo de memoria RAM a
ser usado, initialheap - memória RAM a ser usada
inicialmente, etc):
lucene.initVM(lucene.CLASSPATH)
lucene.initVM(lucene.CLASSPATH,
maxheap=800m)
lucene.initVM(lucene.CLASSPATH,
maxheap=2g)
lucene.initVM(lucene.CLASSPATH,
initialheap=800m)
lucene.initVM(lucene.CLASSPATH,
initialheap=200m, maxheap=800m)
O resultado que se deve obter, numa consola de Python, é
semelhante a:
>>> import lucene
>>> lucene.initVM(lucene.CLASSPATH)
<jcc.JCCEnv object at 0x8192240>
>>>
Visto como iniciar a máquina virtual, pode-se agora tirar
partido de todo o potencial da biblioteca.
Vamos começar por criar um indexador, ou seja, um script
que crie um indíce a partir de uma pasta contendo ficheiros
de texto, no formato txt, por uma questão de simplicidade.
É de ter em atenção que, ao contrário dos ficheiros txt, os
ficheiros como por exemplo os PDF precisam de um parser
para os ler, uma vez que ao Lucene é alimentado o conteúdo
do ficheiro, e não o ficheiro em si. A lógica por detrás do
funcionamento do Lucene já foi explicada anteriormente.
<9>
import lucene, os
lucene.initVM(lucene.CLASSPATH) #
Iniciar a máquina virtual
a programar
Indexador.py
indexPath = "Texto/Index" # indicar o
caminho da pasta com os ficheiros
analyzer = lucene.StandardAnalyzer() #
criar o analisador
indexDir =
lucene.FSDirectory.getDirectory(indexPa
th) # criar o índice
De seguida, podemos escrever em meia dúzia de linhas um
pesquisador para comprovarmos que o nosso índice foi bem
construido. No meu exemplo, indexei apenas 2 ficheiros,
cada um com a seguinte frase:
hello.txt – “Hello World?”
goodbye.txt – “Python is a wonderful language!”
Pesquisador.py
indexWriter =
lucene.IndexWriter(indexDir, analyzer)
# criar o escritor do índice
import lucene
for root, dirs, files in
os.walk('Texto'): # percorremos os
ficheiros da pasta
for eachfile in files:
fileinfo = eachfile. split('.')
filename = '
'.join(fileinfo[:-1])
if fileinfo[-1] == 'txt': #
Para assegurar que só lemos ficheiros
de texto simples
directory =
lucene.FSDirectory.getDirectory('Texto/
Index') # apontar para a directoria do
indice
lucene.initVM(lucene.CLASSPATH)
searcher =
lucene.IndexSearcher(directory) #
criar o pesquisador
analyzer = lucene.StandardAnalyzer() #
criar o analisador
term = raw_input('Escreva o termo a
pesquisar: ')
document =
lucene.Document() # Criamos um
documento pronto a ser inserido no
índice
query = lucene.QueryParser("contents",
analyzer).parse(term) # Passamos o
termo a procurar para o QueryParser,
de modo a ser analisado, apontando
tambem que campo do indice deve ser
procurado.
document.add(lucene.Field(("filename"),
filename, lucene.Field.Store.YES,
lucene.Field.Index.UN_TOKENIZED)) #
Adicionamos o campo do nome do
ficheiro, que vai ser posteriormente
devolvido em pesquisas ao utilizador
hits = searcher.search(query) #
Recuperamos os resultados
if len(hits)>0:
results = [hits[x] for x in
range(len(hits))] # Criamos uma lista
com os documentos dos resultados
filenames = [doc. get("filename")
for doc in results] # recuperamos o
campo filename de cada resultado
scores = [hits.score(x) for x in
range(len(hits))] # recuperamos a
pontuação que cada documento teve na
pesquisa
contents =
open(os.path.join(root, eachfile),
'r').read()
document.add(lucene.Field(("contents"),
contents, lucene.Field.Store.NO,
lucene.Field.Index.TOKENIZED)) # Campo
com o conteudo do ficheiro, tokenizado
e não armazenado.
for i in range(len(filenames)):
print "%s, %s" %(filenames[i],
scores[i]) # Devolvemos os resultados
else:
print "Sem resultados!"
indexWriter.addDocument(document) #
Adiciona o documento ao indice
indexWriter.close() # Fechamos o
escritor
<10>
a programar
Conclusão
O resultado deste script será:
joao@jUbuntu:˜/Desktop$ python
pesquisador.py
Escreva o termo a pesquisar: python
goodbye, 0.5
joao@jUbuntu:˜/Desktop$ python
pesquisador.py
Escreva o termo a pesquisar: hello
hello, 0.625
joao@jUbuntu:˜/Desktop$ python
pesquisador.py
Escreva o termo a pesquisar: portugal
Sem resultados!
A biblioteca Lucene permite ao programador criar
rapidamente um motor de busca que pode ser usado como
aplicação por si só, ou para ser incorporado noutra, ou num
website. Apesar de drasticamente simples, pode tomar
contornos bastante complexos à medida que se vagueia
pelas suas funcionalidades, permitindo uma personalização
e uma costumização fenomenal, ideal para perfeccionistas
que querem lidar com todos os pormenores, ou para aqueles
que precisam de um sistema com um determinado conjunto
de características. Para além disso, é grátis, de código livre,
de maneira a que para ser usada não precisamos de
dispender nenhum tostão e podemos sempre dar uma
olhadela ao código por detrás do seu funcionamento para
ganhar um maior conhecimento.
Estranhamente, João Rodrigues não vem das hostes dos
informáticos tendo tido o primeiro contacto com a programação
no 3º ano do curso. Um descontraído finalista da Licenciatura em
Bioquímica em Coimbra, com particular interesse pelas áreas da
Bioinformática e da Proteómica, entrou para a Revista
PROGRAMAR e com o objectivo de mostrar que há mais na
informática para além de bits e bytes.
João Rodrigues
<11>
a programar
teóricos e passemos para algo mais prático. Por uma
questão de hábito e facilidade de demonstração, a
linguagem usada irá ser Haskell.
Programas Circulares
RepMin
Comecemos pelo exemplo clássico repmin, o problema é o
seguinte.
Introdução
Introduzido originalmente como demonstração do poder da
“lazy evaluation” nas linguagens funcionais nos anos 80, os
programas circulares são considerados como uma técnica
poderosa, concisa, eficiente e elegante para resolver um
particular tipo de problema. Se um algoritmo tiver de
percorrer uma estrutura de dados várias vezes, numa
linguagem de avaliação atrasada, o mesmo poderá ser
expresso com uma única travessia.
Como o nome indica, programas circulares caracterizam-se
por a sua definição ter uma aparência circular, isto é, um dos
argumentos de uma função depende de um dos argumentos
que a mesma função retorna “x = f(x)”. Numa linguagem
com “strict evaluation” nunca irá terminar, enquanto que
numa linguagem “lazy” às vezes conseguirá terminar. A
avaliação atrasada irá sempre obter a ordem correcta de
processamento, se essa ordem de facto existir. Assim, a
função f pela sua definição será virtualmente circular, já que
no momento da sua avaliação irá ser decomposta em não
circular.
No entanto, também é sabido que os programas circulares
são difíceis de entender e escrever. Facilmente o
programador define um programa realmente circular, ou
seja, que nunca termine. Mas deixemos de conceitos
Imaginemos que temos uma árvore binária de folhas,
queremos obter uma nova árvore morfologicamente
idêntica à original, mas agora todas as suas folhas terão que
ter o valor mínimo da árvore original. ( o valor mínimo que a
árvore criada terá, tem como referência aos valores das
folhas iniciais).
data Arvore = Folha Int
-Uma folha terá um valor que será um
inteiro
| Nodo Arvore Arvore -- Um
nodo terá duas sub-árvores
Agora para resolver o problema iremos criar a função
transforma que irá pegar numa árvore e devolver uma nova
árvore.
transforma :: Arvore -> Arvore
transforma arv = replicaArv arv
(minArv arv)
Como podemos constatar a função transforma irá dar uso a
duas funções.
A função minArv que irá calcular o valor mínimo de uma
árvore:
<12>
a programar
Simples.
Vejamos agora no caso de ser um nodo.
minArv :: Arvore -> Int
minArv (Folha x) = x
minArv (Nodo e d) = min (minArv e)
(minArv d)
repmin ((Nodo e d), m) = (replicaArv
(Nodo e d) m, minArv (Nodo e d))
A função replicaArv que irá replicar uma dada árvore com
um valor mínimo.
Mais uma vez, usamos as definições das funções replicaArv e
minArv.
replicaArv :: Arvore -> Int -> Arvore
replicaArv (Folha _) m = Folha m
replicaArv (Nodo e d) m = Nodo
(replicaArv e m) (replicaArv d m)
repmin ((Nodo e d), m) = (Nodo
(replicaArv e m) (replicaArv d m), min
(minArv e) (minArv d))
Como podemos ver, a árvore irá ser percorrida duas vezes.
Uma primeira vez para obter o valor mínimo da árvore e
uma segunda vez para replicar a árvore com o valor mínimo.
Agora o passo seguinte poderá não parecer tão intuitivo.
Mas visto que tanto a função replicaArv como a função
minArv seguem o mesmo padrão de recursividade, podemos
usar a própria definição de repmin para simplificar ainda
mais o código.
Resolução Circular (Uma travessia)
Vejamos agora como podemos transformar o código de
cima ficando um programa circular. A forma mais simples de
efectuar essa transformação é recorrendo à fusão das duas
funções através do uso de tuplos. Portanto começamos a
definir a função repmin como o par das funções replicaArv e
minArv. A nossa função repmin irá receber uma árvore e um
hipotético valor mínimo.
repmin ((Nodo e d), m) = (Nodo e' d',
min m1 m2)
where (e', m1) = repmin (e, m)
(d', m2) = repmin (d, m)
Bem, se for um Nodo também não há muito mais a fazer.
Recapitulemos como temos definido a nossa função repmin:
repmin :: (Arvore, Int) -> (Arvore,
Int)
repmin ((Folha x), m) = (Folha m, x)
repmin ((Nodo e d), m) = (Nodo e' d',
min m1 m2)
where (e', m1) = repmin (e, m)
(d', m2) = repmin (d, m)
repmin :: (Arvore, Int) -> (Arvore,
Int)
repmin (a, m) = (replicaArv a m,
minArv a)
Vamos agora tentar fundir as duas funções, simplificando o
código. Ora uma árvore pode ser folhas ou nodos, portanto
temos que ter os dois casos. Comecemos pelas folhas por
ser mais simples. Então caso seja uma folha, a linha de cima
fica da seguinte forma:
repmin ((Folha x), m) = (replicaArv
(Folha x) m, minArv (Folha x))
Bom, dado uma árvore e um hipotético valor mínimo,
replica essa árvore com esse valor hipotético e ao mesmo
tempo calcula o valor mínimo da árvore, tudo em apenas
uma travessia. Ainda melhor era conseguirmos dizer que
aquele valor hipotético era o valor mínimo calculado por
nós. E é aqui que entra a função circular, redefinamos então
a função transforma para nos permitir tal proeza.
Usando a definição de replicaArv e minArv obtemos um
código equivalente, simplificando-o ainda mais.
repmin ((Folha x), m) = (Folha m, x)
<13>
transforma :: Arvore -> Arvore
transforma arv = arv'
where (arv', m) = repmin (arv, m)
a programar
E está feito. De salientar o uso do símbolo m nos dois lado
da função repmin, ou seja, é ao mesmo tempo o argumento
e o resultado da função. No momento de execução da
função transforma, o Haskell devido a ser lazy como que por
magia irá transformar a árvore com um valor que só terá
acesso no futuro, é como se fosse possível viajar no tempo.
Já que estamos a trabalhar apenas com funções, para
começar podemos dizer que a nossa Arrow a é uma função.
Então loop fica:
Arrows
Visto que o nosso input e output irão ser árvores, portanto o
nosso ‘b’ e ‘c’ irão ser do tipo Arvore. O feedback irá ser o
nosso hipotético valor minimo, ou seja, o ‘d’ será um Int.
Fazendo estas substituições na assinatura do loop ficamos
com:
O exemplo foi feito de forma a demonstrar totalmente o
processo de converter o código para a versão circular. Na
verdade, o último passo é desnecessário. Haskell possui
Arrows, um conceito similar às populares Monads mas mais
generalista.
Ora com arrows uma pessoa consegue com bastante
facilidade descrever e conjugar computações. E como já
devem estar à espera, existe uma arrow já pré-definida na
biblioteca para o nosso caso. Essa arrow tem de nome loop e
tem o seguinte esquema.
loop :: ( (b, d) -> (c, d) ) -> (b ->
c)
loop :: ( (Arvore, Int) -> (Arvore,
Int) ) -> (Arvore -> Arvore)
Já está mais agradável. A função loop recebe como
argumento uma função e devolve outra função. Se vermos
com atenção, vimos que a assinatura desta função (a de
argumento) é exactamente a mesma que a da nossa função
repmin. De facto, para pormos a função repmin como
circular basta passar para a função loop como argumento.
Visto isto, podemos redefinir a nossa função transforma
usando a biblioteca Control.Arrow.
transforma :: Arvore -> Arvore
transforma = loop repmin
A função loop é em si uma arrow que possui internamente
uma outra arrow que dado um input irá devolver um ouput
com um auxílio de um feedback. Como podem reparar este
feedback irá ser a nossa referencia circular.
Do ponto de vista do Haskell, a função loop tem a seguinte
assinatura.1)
loop :: (Arrow a) => a (b, d) (c, d) > a b c
Analisando a assinatura, podemos ver que a arrow interna
tem de ser do tipo ‘a (b, d) (c, d)’. Vamos ter o input do tipo
‘b’ , o output do tipo ‘c’ e o feedback será do tipo ‘d’. No fim
vamos ter como resultado a arrow ‘a b c’, ou seja, uma
computação que dados aas devolve bbs. Vejamos como
podemos usar isto no nosso caso.
1) Não é totalmente verdade, de facto o loop é um ArrowLoop que
é uma extensão da classe Arrow, mas simplifica o processo de
explicação.
Média
Agora que já demonstrámos um método simples de
transformar um programa com múltiplas travessias em
apenas uma, vamos ver outro exemplo clássico, pois este
método simples está longe de ser perfeito, e nem sempre
produz resultados ideais. Mas que se estivermos atentos
facilmente contornamos o problema. Temos então o
seguinte problema.
Dada uma lista de valores, queremos calcular a média da
lista e incrementar cada valor da lista pela média obtida.
Assim de imediato vemos que temos três travessias na lista.
Uma travessia para calcular o número de elementos na lista,
uma segunda travessia para somar os elementos da lista e
uma terceira travessia para incrementar cada valor da lista
pela média. De salientar que a primeira travessia e a
segunda travessia não dependem uma da outra e portanto é
muito simples fazer a sua fusão e é apenas necessário uma
travessia. Mas não é esse o ponto em questão, e é para
reforçar a ideia, que para cada posição da lista temos que
efectuar três operações.
<14>
a programar
Bem, não vou estar agora a fazer passo a passo, mas à
primeira tentativa o mais provável era obtermos algo do
género.
Já que se está a usar Arrows pode-se sempre pôr o código
ainda mais conciso, apesar de se tornar um pouco menos
legível. Fica ao gosto de cada um a forma que prefere.
transforma :: [Double] -> [Double]
transforma = loop incrMedia
transforma :: [Double] -> [Double]
transforma = loop (incrMedia >>> id
*** uncurry (/))
incrMedia :: ([Double], (Double,
Double)) -> ([Double], (Double,
Double))
incrMedia ([], _) = ([], ( 0,0))
incrMedia ((x:xs), (soma, numElems)) =
( (x + media) : xs', (soma' + x,
numElems' + 1))
where (xs', (soma', numElems')) =
incrMedia (xs, (soma, numElems))
media = soma / numElems
incrMedia :: ([Double], Double) ->
([Double], (Double, Double))
incrMedia ([], _) = ([], ( 0, 0))
incrMedia ((x:xs), media) = incrMedia
>>> ((x +
media):) *** ((+x) *** (+ 1))
$ (xs,
media)
O problema deste código é que se formos executar com
uma lista nunca irá terminar. O problema está no cálculo da
média em todo os passos. Podemos solucionar este
problema separando o cálculo da média da travessia da lista,
isto é, já termos o nosso feedback (a média) já calculada. A
versão correcta seria então:
Conclusão
Este artigo tinha como objectivo dar a conhecer o conceito
de programas circulares e espero que tenha tido sucesso. Foi
feita apenas uma pequena introdução do conceito, com o
auxílio de um método muito simples de transformação de
programas. O método aqui indicado tem várias falhas.
transforma :: [Double] -> [Double]
transforma = loop incrMedia
• Nem sempre se obtêm um programa circular válido, que
termine.
incrMedia :: ([Double], Double) ->
([Double], Double)
incrMedia (lista, media) = (lista',
soma / total)
where (lista', (soma, total)) =
incrMedia' (lista, media)
• As várias travessias tem de seguir o mesmo padrão
recursivo.
• Devido ao ponto anterior, as travessias tem de ser sempre
sobre a mesma estrutura de dados.
incrMedia' :: ([Double], Double) ->
([Double], (Double, Double))
incrMedia' ([], _) = ([], ( 0, 0))
incrMedia' ((x:xs), media) = ((x +
media) : xs', (soma + x, total + 1))
where (xs', (soma, total)) =
incrMedia' (xs, media)
Como podemos verificar as duas soluções são muito
parecidas, mas na última versão inserimos uma função extra
no meio para separar o cálculo da média da travessia da
lista. É necessário, visto que se tivermos um valor hipotético
de média, a assinatura da função não encaixa, já que a cada
passo o feedback irá ser o somatório e a contagem dos
elementos da lista. Daí termos uma função extra para servir
de adaptador, para o nosso feedback ter o mesmo tipo,
tanto à entrada como à saída.
O último ponto é bastante importante. Muitos casos tornam
necessário a formação de estrutura de dados intermédia
entre as várias travessias. Por exemplo, no caso do exemplo
da média, se quiséssemos que a lista obtida estivesse
ordenada, então o mais normal era usar como uma estrutura
intermédia uma árvore binária e implementar algo tipo o
quicksort ou mergesort. Este novo problema deita logo
abaixo o nosso método simples. Para quem tiver interessado
recomendo que pesquise um pouco, esta é uma área com
alguma investigação e existem vários papers onde são
abordados novos métodos (mais complexos que não se
enquadram neste artigo), mas que ultrapassam estes
problemas referidos.
De salientar, que apesar de ser uma área um pouco obscura,
já começa a ver-se alguns casos práticos de programas
circulares. Sendo os mais usuais, o “pretty-printing” em que
são necessárias várias travessias por questões de
formatação ou então compiladores em que também é
<15>
a programar
necessário várias travessias para calcular por exemplo os
endereços de “jump”.
Extras
• Página do Haskell:
http://haskell.org/
• Introdução a Arrows:
http://www.haskell.org/arrows/index.html
• Pequeno artigo sobre programas circulares que serviu de
forte inspiração:
http://www.haskell.org/haskellwiki/Circular_programming
• The Monad Reader Issue 6 (Assembly: Circular
Programming with Recursive do):
http://www.haskell.org/sitewiki/images/1/14/TMRIssue6.pdf
Natural de Santa Maria da Feira, Miguel Bento encontra-se desde
Novembro 2006 na Creativesystems, uma das principais líderes na
área de RFID na península Ibérica. Tem como preferência a nível
industrial a linguagem C# mas aproveita o seu tempo livre para
aprofundar os seus conhecimentos na sua linguagem predilecta,
Haskell.
MiguelBento
Bento
Miguel
<16>
a programar
que possam ser usados nas várias camadas. Podem ser
incluídas mais ou menos camadas, que o conceito mantemse - há a classe de suporte às propriedades, global às
camadas, e um conjunto de classes gestoras, em cada
camada.
Serialização e
desserialização de
Objectos em C#
Introdução
Num projecto recente, desenvolvi um sistema multi-tier em
que alguns dados eram provenientes de ficheiros XML, em
que também era necessário escrever dados para ficheiros
em XML. Na definição dos objectos de negócio da aplicação,
além das propriedades estavam incluídos dois métodos,
ToXML() e FromXML(XmlNode), que convertiam os dados
do objecto para XML ou recebiam os dados em XML e
preenchiam o objecto com esses dados.
Inicialmente, os métodos de conversão de XML para objecto
e de objecto para XML estavam incluídos na classe que
definia o objecto, com a personalização necessária à classe.
Esta abordagem era evidentemente má, especialmente
quando o número de classes era grande pois não permitia a
reutilização do código. Com tempo foi possível desenvolver
uma classe generalizada, que permite a serialização e
deserialização em XML dos objectos das diversas aplicações
que integram a classe, aumentando a eficiência de
desenvolvimento.
O artigo que segue, apresenta a estrutura típica em que é
usado, a classe XmlCustomSerializer que efectua a
serialização e desserialização do objecto automaticamente,
e um exemplo de como utilizá-la.
Enquadramento
As aplicações que tenho desenvolvido, essencialmente na
framework .NET (ASP.NET com C#), têm mantido a mesma
estrutura multicamada. Aprendi este método e adaptei-o às
minhas necessidades após a leitura de um conjunto de
artigos do Imar Spaanjaars (http://imar.spaanjaars.com –
“Building Layered Web Aplications With Microsoft ASP.NET
2.0”).
Resumidamente, o método que ele descreve inclui uma
camada de acesso a dados (DAL) uma camada de lógica de
negócio (BLL) e a camada de apresentação. Comuns a estas
3 camadas estão os objectos de negócio (BO) que pouco
mais são que contentores de objectos – classes definidas
apenas com propriedades e, nalguns casos, alguns métodos
A DAL tem tipicamente o conjunto de métodos CRUD de
acesso à base de dados ou repositório de ficheiros. As
classes da camada são gestores dos objectos no que diz
respeito a acesso a dados. Por exemplo uma classe Pessoa,
classe que é um BO com as propriedades de uma pessoa da
aplicação, tem na DAL uma classe PessoaDAL, com o
conjunto de métodos CRUD para gerir os dados de Pessoa
na base de dados ou repositório. A classe tem os métodos
Get(), GetList(), Insert(Pessoa), Update(Pessoa),
Delete(Pessoa) e outro relevantes.
A BLL é composta também por classes gestoras dos
objectos, mas com os métodos relevantes à camada de
negócio – validações, autenticação, processamento de
dados recebidos da aplicação ou da DAL, etc. Em alguns
casos, os métodos da BLL são simples chamadas do método
semelhante na DAL. A camada de apresentação tem o
conjunto de métodos relevantes à apresentação da
informação na aplicação e processamento das acções do
utilizador.
Esta estrutura permite tornar transparente a forma como
funcionam as diversas camadas. Apenas passa o conjunto de
dados gerado pelas camadas para a seguinte, e por exemplo
a camada de apresentação nunca terá acesso directo à
camada de escrita de dados. Mais, é possível separar e
distribuir as diversas camadas através de Remoting e/ou
Web Services partilhando recursos entre aplicações ou
distribuindo o esforço por diversas máquinas.
Serialização no .NET
A serialização é um mecanismo importante na framework
do .NET, em que os objectos são transformados numa
stream de dados, formato portável, quer para utilizar
remotamente, quer para persistir dados. No caso dos
WebServices os dados das respostas são convertidos para
XML, e o objecto a partir desses dados pode ser
reconstruído. A serialização e desserialização neste caso é
realizado automaticamente pela framework.
Na framework há três modos de serialização:
• Binária – mais leve e que apresenta a melhor performance
e usado no remoting.
• SOAP – usado por Webservices para converter os objectos
em XML.
• XML – serialização costumizada para um formato
desejado.
<17>
a programar
Quer a serialização binária quer a SOAP efectuam Deep
Serialization no sentido que serializam o objecto na sua
totalidade, incluindo outros objectos contidos nele. Já o
XML, porque apenas serializa os elementos públicos do
objecto, e é feita da forma como nós indicamos, é chamada
de Shallow Serialization.
O resto do artigo está ligado à serialização costumizada em
XML. É bastante útil para passar dados que estão em
ficheiros XML directamente para um objecto ou vice versa.
Há aplicações em que uma base de dados é overkill, e meia
dúzia de ficheiros XML resolvem para suportar os dados.
Escrever código dedicado a cada objecto é também muitas
vezes desnecessário (a menos que siga um formato muito
próprio e os objectos em uso não admitam a conversão
directa).
ver no código, a DAL tem o conjunto de métodos que
permite aceder à fonte de dados e retornar os objectos.
Apenas esta camada tem acesso aos dados, o que torna o
processo de obtenção de dados transparente para as
camadas superiores. A BLL muitas vezes é uma simples
interface para os métodos da DAL, podendo ter mais
processamento, como validação e autenticação.
A camada de apresentação é constituída pelos vários
formulários que a aplicação pode ter. Estes formulários
apenas conhecem os métodos públicos da BLL que por sua
vez apenas conhece os métodos públicos da DAL. A
separação é implementada através de namespaces, e cada
camada conhece apenas a camada seguinte. Este modo
implementa uma clara separação da funcionalidade de cada
camada.
Para o demonstrar vou criar um pequeno exemplo. Este
servirá para apresentar o modo de implementação dos
objectos como contentores, a classe genérica de serialização
e a sua aplicação.
Exemplo - lista de contactos
Um bom exemplo é uma lista de contactos. Os objectos de
negócio que vou utilizar são:
A classe Pessoa contém os dados de uma pessoa (ID, nome
e contactos) e utiliza ContactoList para associar vários
contactos ao mesmo registo de Pessoa. ContactoList não é
mais que uma lista genérica do tipo Contacto que contem os
dados do contacto individual (tipo de contacto e o valor).
PessoaList é também uma lista genérica, mas do tipo
Pessoa.
A aplicação exemplo não utiliza as camadas BLL e DAL (a
simplicidade não exige). No entanto declarei-os para
apresentar um exemplo de como funciona. Como é possível
Relativo à aplicação, a funcionalidade implementada é o
mínimo necessário para demonstrar o serializador. Primeiro,
tem um campo que permite criar uma instância do objecto
Pessoa através do nome. O ID é atribuído automaticamente
pelo processo de inserção na lista PessoaList. À última
pessoa criada é possível associar uma lista de contactos
adicionando um contacto de cada vez. A caixa de texto
apresenta o XML gerado pelo serializador a partir da
<18>
a programar
instância do objecto PessoaList que suporta os dados. Por
fim, é possível converter os dados do XML em numa nova
instância de PessoaList.
Contacto c = new
Contacto(textBox2.Text,textBox4.Text);
A classe Form1 do formulário tem um membro declarado do
tipo PessoaList para suportar a lista de pessoas:
if (aLista[aLista.Count 1].Contactos == null)
aLista[aLista.Count 1].Contactos = new ContactoList();
public partial class Form1 : Form
{
PessoaList aLista = new PessoaList();
aLista[aLista.Count 1].Contactos.Add(c);
ShowXML();
}
A adição de uma pessoa cria um objecto pessoa, com o
nome preenchido e adiciona-o à lista de pessoas:
É criado um novo contacto com o par valor / tipo no
construtor. Se a lista de contactos da pessoa é nulo, é criado
nova instância da lista. Finalmente é adicionado o contacto à
lista e armazenada a lista.
private void button1_Click(object
sender, EventArgs e)
{
Pessoa p = new
Pessoa(textBox1.Text);
aLista.Add(p);
Serializador costumizado
Preparação dos objectos
//o ID neste exemplo simples
passa a ser a posição na lista
aLista[aLista.Count- 1].ID =
aLista.Count;
Porque pretendemos serializar os objectos de forma
automática para um formato costumizado, temos que
preparar os objectos para essa tarefa. O .Net tem um
conjunto de atributos que devemos adicionar ao código dos
objectos para tornar a construção e desconstrução do
objecto em XML possível. Vamos primeiro analisar a classe
Contacto:
//Com o construtor certo, as
três linhas de cima poderiam ser
escritas numa só, do género:
// aLista.Add(new
Pessoa(aLista.Count, textBox1.Text));
//com ID inicial 0
public class Contacto
{
public Contacto() { }
//SaveList - aqui surgiria a
chamada ao processo para armazenar os
dados.
ShowXML();
}
A criação do ID é simulado neste caso – o ID após a inserção
na lista é criado a partir do índice do registo na lista. O
processo de armazenar os dados também é simulado,
apenas mostrando o XML gerado na caixa de texto (o
ShowXML será descrito mais à frente.)
Para adicionar contactos temos:
private void button2_Click(object
sender, EventArgs e)
{
public Contacto(string
contacto, string tdc)
{
this.Valor = contacto;
this.Tipo = tdc;
}
private string _valor;
[XmlElement]
public string Valor
{
get { return _valor; }
set { _valor = value; }
}
private string _tipo;
<19>
a programar
atributo XMLType com a propriedade TypeName sobre a
classe permite alterar o nome com que é serializado. O
mesmo é possivel com o XmlElement através do parametro
ElementName.
[XmlAttribute]
public string Tipo
{
get { return _tipo; }
set { _tipo = value; }
}
A classe Pessoa e PessoaList são:
}
A classe tem dois construtores, um vazio (necessário para o
serializador) e outro parametrizado. Contém também as
propriedades, com membros públicos e privados. O atributo
essencial para o processo de serialização é o que está
declarado imediatamente antes do membro público.
[XmlElement] antes de Valor, indica que valor deve ser
serializado como sendo um elemento XML; [XmlAttribute]
antes de Tipo indica que Tipo será um atributo do elemento
Contacto. É importante notar que os tipos nuláveis (ainda)
não são serializáveis como XmlAttribute. O objecto
serializado terá a seguinte estrutura:
<Contacto Tipo="telemovel">
<Valor>912345678</Valor>
</Contacto>
ContactoList, que é declarado como:
[XmlType(TypeName="Contactos")]
public class ContactoList :
List<Contacto>
{
}
Será serializado com a forma:
<Contactos>
<Contacto Tipo="telemovel">
<Valor>912345678</Valor>
</Contacto>
<Contacto Tipo="email">
public class Pessoa
{
/// <summary>
/// construtor vazio é
necessário para a serialização
/// </summary>
public Pessoa() { }
public Pessoa(string nome)
{
this.Nome = nome;
}
/// <summary>
/// id é nullo quando se
adiciona o contacto, logo usemos um
elemento nulável
/// </summary>
private int? _id = null;
[XmlElement(IsNullable=true)]
public int? ID
{
get
{
if (_id.HasValue)
return _id.Value;
else return null;
}
set
{
if (value.HasValue)
_id = value.Value;
else _id = null;
}
}
private string _nome =
String.Empty;
[XmlElement]
public string Nome
{
get { return _nome; }
set { _nome = value; }
}
<Valor>alho@miguelalho.com</Valor>
</Contacto>
(...)
</Contactos>
Por defeito, as listas, sem qualquer modificador no seu
atributo, são serializadas como “ArrayOfTipo” (no caso de
ContactoList seria ArrayOfContacto). A utlização do
<20>
private ContactoList
_contactos = null;
[XmlArray(IsNullable=true)]
a programar
<Pessoas
xmlns:xsi="http://www.w3.org/2001/XMLSc
hema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSc
hema">
<Pessoa>
<ID>1</ID>
<Nome>Miguel Alho</Nome>
<Contactos>
<Contacto Tipo= "telemovel">
<Valor>912345678</Valor>
</Contacto>
<Contacto Tipo= "email">
public ContactoList Contactos
{
get { return _contactos; }
set { _contactos = value;
}
}
}
[XmlType(TypeName = "Pessoas")]
public class PessoaList :
List<Pessoa>
{
<Valor>alho@migeulalho.com</Valor>
</Contacto>
</Contactos>
</Pessoa>
<Pessoa>
<ID>2</ID>
<Nome>outra pessoa</Nome>
<Contactos>
<Contacto Tipo= "email">
<Valor>outro@email</Valor>
</Contacto>
</Contactos>
</Pessoa>
</Pessoas>
}
Aqui, a primeira novidade é o uso de [XmlArray] para indicar
que Contactos (do tipo ContactoList) é efectivamente uma
lista e deve ser apresentado no XML como um array de
Contacto.
A segunda novidade é o IsNullable no atributo de
[XmlElemento] da propriedade ID e no [XmlArray] . Caso
seja nulo, no XML gerado será escrito na tag xsi:nil=“true”
indicando explicitamente que é nulo. Sem o IsNullable, no
caso de o valor da propriedade ser nulo, o respectivo XML é
ignorado. Caso haja algum elemento público que não
desejamos passar para o XML (como um método que
retorna um valor baseado nas propriedades) podemos usar o
[XmlIgnore].
Usando o programa exemplo, se adicionar uma Pessoa
(instanciar uma pessoa com o nome preenchido), o XML
gerado é:
Os objectos que são arrays são correctamente convertidos
para listas de objectos, as propriedades são correctamente
serializadas e apresentadas da forma que escolhi. Resta ver
então como fazer a serialização, propriamente.
XmlCustSerializer
<Pessoas
xmlns:xsi="http://www.w3.org/2001/XMLSc
hema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSc
hema">
<Pessoa>
<ID>1</ID>
<Nome>Miguel Alho</Nome>
<Contactos xsi:nil=” true”/>
</Pessoa>
</Pessoas>
A classe XmlCustSerializer é que efectua a conversão de e
para Xml. Está incluída no namespace dos BO para poder ser
usada nas diversas camadas da aplicação. É escrita na forma
de classe genérica de modo a suportar qualquer tipo que
definimos.
A informação do namespace (xmlns:xsi…) é adicionada
automaticamente pelo serializador e apenas no primeiro
elemento.
Se adicionarmos mais pessoas e contactos, temos:
<21>
public class XmlCustSerializer<T>
{
public static XmlElement ToXml(T obj)
//XmlDocument xd) {
{
//cria stream na memória para
suportar o Xml
MemoryStream memoryStream =
new MemoryStream();
a programar
forma automática, sem código especifico a determinado
objecto.
try
{
//Instancia o serializador
XmlSerializer xs = new
XmlSerializer(obj.GetType());
//Instancia o
XmlTextWriter
XmlTextWriter
xmlTextWriter = new
XmlTextWriter(memoryStream,
Encoding.UTF8);
//Serializa os dados da
classe para o writer
public static T FromXml(XmlNode
O_Xml)
{
//Instancia uma representação
de encoding UTF-8
UTF8Encoding encoding = new
UTF8Encoding();
try
{
xs.Serialize(xmlTextWriter, obj);
//Instancia uma Stream de
memória, para onde é passado o texto
Xml do parâmetro de entrada
MemoryStream memoryStream
= new
MemoryStream(encoding.GetBytes(O_Xml.Ou
terXml));
//retorna o apontador da
stream para o início
memoryStream.Position = 0;
//instancia a serialização
para o tipo de dados
XmlSerializer xs = new
XmlSerializer(typeof(T));
//Copia a stream do
TextWriter para a memória
memoryStream =
(MemoryStream)xmlTextWriter.BaseStream;
//Coloca o apontador para
o início da stream
//(necessário para evitar
erros no carregamento dos XMlDoc)
memoryStream.Position = 0;
//Instancia o XmlDocument
XmlDocument xd = new
XmlDocument();
//carrega o Stream em XMl
para o XmlDocument
xd.Load(memoryStream);
//Obtém o nó raiz com os
dados a retorna
XmlElement node =
xd.DocumentElement;
//retorna o nó raiz.
return node;
}
catch
{
throw new
InvalidCastException ("Ocorreu um erro
na serialização do objecto
(1009002001)");
}
//return registo;
}
T obj;
obj =
(T)xs.Deserialize(memoryStream);
return obj;
}
catch
{
throw new
InvalidOperationException ("Ocorreu um
erro na desserialização do objecto
(1009002002)");
}
}
}
O ToXml recebe um objecto do tipo T e converte-o em Xml,
consoante os atributos da classe. O processo requer a
serialização para uma stream de memória através de um
XmlTextWriter. Posteriormente o stream é lido para um
XMLDocument e o elemento XML é retornado. Como é
possível verificar, o XmlSerializer efectua a serialização de
O método FromXML(XmlNode) efectua o processo inverso –
a partir de um nó XML, com o encoding em UTF-8
(possivelmente o método poderá ser alterado para aceitar o
encoding como parâmetro), transfere-o para a stream de
memória, desserializa-o para o o objecto do tipo T e retorna
o objecto com os dados.
Para o usar, basta usar a classe para o tipo desejado, por
exemplo:
<22>
a programar
Da mesma forma que podem haver ausência de tags, pode
existir tags e atributos redundantes:
private void ShowXML()
{
textBox3.Text =
XmlCustSerializer<PessoaList>.ToXml(aLi
sta).OuterXml;
}
onde aLista é do tipo PessoaList, e o uso de .OuterXml
permite obter uma string. Porque os métodos são estáticos,
não é necessário criar uma instância da classe
XmlCustSerializer. No caso da desserialização, temos:
private void button3_Click(object
sender, EventArgs e)
{
XmlDocument xDoc = new XmlDocument();
xDoc.LoadXml(textBox3.Text);
dataGridView1.DataSource =
XmlCustSerializer<PessoaList>. FromXml(x
Doc.FirstChild);
}
<Pessoas data="today" origem="a">
<Pessoa numero="1">
<Nome>Miguel Alho</Nome>
<criado modo="automático"/>
</Pessoa>
</Pessoas>
Por exemplo os atributos data, origem, e numero, e o
elemento criado não são relevantes para a nossa aplicação.
No entanto, o objecto PessoaList é construído
correctamente, ignorando o que está a mais. Podemos por
exemplo guardar o ficheiro num repositório para
conservação, mas usar apenas os dados relevantes na
aplicação.
Conclusão
P en s o q ue é eviden t e
XMLCustSerializer.
Na aplicação, o XML contido na área de texto é
transformado e XmlDocument e desserializado para a nova
PessoaList, que serve de datasource ao dataGridView. Neste
exemplo, a serialização e desserialização é usada apenas
para apresentar dados, mas podia ser usada para ler os
dados de um ficheiro ou escrever para um ficheiro, por
exemplo, tornando a conversão automática.
O processo também é bastante permissivo, no sentido que
se retirar elementos do XML, posso construir o objecto na
mesma (desde que a propriedade permita o nulo por
defeito). Por exemplo:
a
ut ilidade
da
c las s e
• Implementa o processo de serialização e desserialização
de objectos da aplicação para XML de forma automática e
customizada.
• Os mesmos métodos aceitam objectos de tipos
customizados, reutilizando código de forma eficiente.
• A conversão para XML permite implementar métodos mais
simples de armazenamento dos dados em ficheiros, e
métodos de leitura dos mesmos.
• O uso de XML é útil para interoperacionalidade da
aplicação.
O uso do XML tem custos, no entanto:
<Pessoas >
<Pessoa>
<Nome>Miguel Alho</Nome>
</Pessoa>
</Pessoas>
• O overhead das tags é grande, e é sentida em ficheiros
com grande quantidade de dados (o XML pode ocupar 10x
mais que um CSV equivalente).
É correctamente reconstruído como objecto PessoaList, em
que as propriedades ID e Contactos da única Pessoa na lista
são nulos. Esta caracteristica pode ser muito útil na
definição de um formato de ficheiro que uma aplicação deva
receber, e que não necessite de ter os dados completos
(diminuindo o tamanho do ficheiro). Depois, após recepção,
o objecto desserializado é processado e os campos nulos
poderão ser validados ou preenchidos pela aplicação.
Uma forma de o contornar é usar attributos onde possível, e
utilizar nomes curtos para os elementos.
• Em comparação aos outros modos de serialização, a
binária é mais rápida.
Eu pessoalmente vou utilizando o formato XML, porque se
necessário, posso editar um ficheiro gerado à mão, e a
conversão directa para objecto facilita a produção de código
de armazenamento e leitura dos ficheiros e facilita o
<23>
a programar
processo de declaração do formato de armazenamento.
• Introducing Serialization in .NET , Joydip Kanjilal
http://aspalliance.com
Bibliografia e Créditos
• Serialization in .NET , Nishith Pathak
http://www.CodeProject.com
A classe XmlCustSerializer foi desenvolvido em conjunto
com Marco Fernandes e Joaquim Rendeiro.
Algumas fontes usadas para compor a parte teórica do
artigo:
• XmlSerializer Class
http://msdn2.microsoft.com
• System.Xml.XmlSerializer, Nitin Pande
http://www.eggheadcafe.com
• Building Layered Web Aplications With Microsoft
ASP.NET 2.0
http://Imar.spaanjaars.com
Natural da Murtosa, Miguel Alho é licenciado em Engenharia
Electrónica e Telecomunicações pela Universidade de Aveiro.
Actualmente trabalha como programador freelancer,
desenvolvendo aplicações web e multimédia para entidades
nacionais e internacionais, utilizando principalmente a
tecnologia .NET da Microsoft.
Miguel Alho
<24>
a programar
o nome de Julho ao sétimo mês do Calendário Juliano, o qual
possui até hoje um ciclo de 31 dias. A partir do ano 8 a.C.,
tempo do imperador César Augusto, houve a mudança do
nome do oitavo mês para Agosto (em sua homenagem), e
como o mês de Julho possuía 31 dias, foi então atribuído
mais um dia ao mês de Agosto, passando este a ter um ciclo
de 31 dias, tal como o mês de Julho e, para que não houvesse
diferença entre as homenagens. Este dia a mais foi retirado
do mês de Fevereiro, que passou a ter um ciclo de 28 ou 29
dias nos anos bissextos. Desde então, ficaram instituídas as
regras para os meses com 31 dias (Janeiro, Março, Maio,
Julho, Agosto, Outubro e Dezembro), com 30 dias (Abril,
Junho, Setembro e Novembro) e com 28 ou 29 dias
(Fevereiro).
Manipulação
de Datas
Introdução
No mundo da programação, é comum necessitarmos de
algum utilitário que efectue cálculos com datas do
calendário gregoriano. Os ambientes de programação
integrados normalmente possuem tais funções já definidas
internamente, bastando ao programador apenas fazer uso
das mesmas. A principal questão deste artigo é responder à
questão: como podem esses cálculos ser processados? Para
solucionar este tipo de problemas, é conveniente conhecer e
utilizar algoritmos específicos. Existem várias formas e
soluções, no entanto é aconselhável utilizar aquelas que já
tenham sido testados como, por exemplo, algoritmos
utilizados na área da Observação Astronómica, que
possibilitam calcular qualquer data a partir do ano 4712 a.C.
Para entender melhor o processo do cálculo de datas, tornase necessário entender os conceitos de: Dia Juliano,
Calendário Juliano, Calendário Gregoriano e Ano Bissexto.
Calendário Gregoriano
O calendário Gregoriano foi instituído na reforma papal do
pontificado do Papa Gregório XIII. Um facto curioso é que o
calendário Gregoriano foi iniciado em 05/10/1582, tendo
uma diferença de 10 (dez) dias em relação ao término do
calendário Juliano. Observe como ficou o calendário do mês
de Outubro de 1582, quando ocorreu a junção dos
calendários Juliano e Gregoriano.
Outubro 1582
Dom
Seg
Ter
Qua
Qui
Sex
Sáb
1
2
3
4
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Dia Juliano
O dia Juliano é um valor sequencial positivo inteiro que
representa uma contagem sucessiva de dias a partir do ano
4712 a.C., ou seja, do ano -4712. A data 01/01/2000 seria
representada como dia Juliano com o valor 2.451.545. Assim
sendo, é possível calcular a diferença entre datas distantes.
Por exemplo, quantos dias existem entre as datas
01/01/2000 e 26/04/1965? Para obter o número de dias entre
as datas, é necessário converter cada uma das datas para os
respectivos valores sequenciais para então efectuar o cálculo
da diferença de dias entre as datas desejadas. Desta forma,
a data 26/04/1965 (2.438.877) subtraída da data 01/01/2000
(2.451.545) resulta no valor 12.688 dias.
Calendário Juliano
A supressão de dez dias ocorreu devido a um erro no ciclo do
número de dias de um ano, existente no calendário Juliano,
que prevê um ano de 365,25 dias, quando este ciclo é de
364,24219271 dias, sendo reduzido a uma razão de 0,005369
segundo por ano.
Ano Bissexto
O calendário Juliano foi instituído no ano 46 a.C. (época do
imperador romano Júlio César) e utilizado até 04/10/1582.
Nesta ocasião foram considerados três anos de 365 dias e
um de 366, a cada quatriénio (ano bissexto). Nesse período,
o mês de Fevereiro possuía 29 dias, tendo 30 dias apenas
nos anos bissextos. Em homenagem ao imperador foi dado
Os cálculos para determinar anos bissextos nos calendários
Juliano e Gregoriano são diferentes. No calendário Juliano é
ano bissexto todo ano divisível por 4 (quatro), porém no
calendário Gregoriano, devido às correcções e para maior
precisão astronómica, considera-se bissexto o ano divisível
<25>
a programar
por 4 (quatro), excepto os anos terminados em 00 que para
serem bissextos necessitam ser divisíveis por 400
(quatrocentos). O ajuste nos anos terminados com 00 ocorre
a cada 400 (anos), devido ao ciclo de um ano possuir
364,24219271 dias, sendo reduzido a uma razão de 0,005369
segundo por ano, ciclo denominado de ano trópico, quando
ocorre o equinócio (quando o Sol está exactamente sobre a
linha do equador no início da primavera ou no início do mês
de Outubro; nesta ocasião o dia e a noite têm a mesma
duração temporal).
Algoritmos
A partir da exposição dos conceitos anteriores é
apresentado os algoritmos utilizados para conversão de
data em dia Juliano, conversão de dia Juliano em data e
verificação de ano bissexto. Conversão de Data em Dia
Juliano Para converter uma data do calendário Juliano ou
calendário Gregoriano em dia Juliano deve-se fazer uso de
um algoritmo específico que transforma uma data num
número sequencial, como o demonstrado:
Leia DIA, MÊS, ANO
Se (MÊS < 3) Então
ANO <- ANO - 1
MÊS <- MÊS + 12
Fim_se
Se (DATA >= 15/10/1582) Então {Calendário
Gregoriano}
A <- Inteiro(ANO / 100)
B <- Inteiro(A / 4)
C <- 2 - A + B
Senão {Calendário Juliano}
C <- 0
Fim_se
D <- Inteiro(365,25 * (ANO + 4.716))
E <- Inteiro(30,6001 * (MÊS + 1))
F <- D + E + DIA + 0,5 + C - 1.524,5
JULIANO <- Inteiro(F)
Escreva JULIANO
O algoritmo de conversão verifica primeiro se o MÊS é
menor que três. Sendo a condição verdadeira, é feito um
ajuste nos valores de ANO e MÊS. Em seguida, é verificado
se a data fornecida pertence ao calendário Gregoriano
(datas a partir de 15/10/1582) ou calendário Juliano (datas
anteriores a 04/10/1852). Se a data pertencer à parte do
calendário Gregoriano, a variável C é ajustada a partir das
variáveis A e B; caso contrário, à variável C é atribuído o
valor de zero. Após a verificação da condição e estando a
variável C ajustada, são realizados cálculos para determinar
o número de dias a partir de 4712 a.C.
Conversão de Dia Juliano em Data
Para converter um dia Juliano em data é necessário também
seguir um algoritmo específico de conversão, que tem em
consideração o facto de um ano ser ou não bissexto.
Leia JULIANO
A <- JULIANO
Se (A > 2.299.160) Então
B <- Inteiro((A - 1.867.216,25) /
36.524,25)
C <- A + 1 + B - Inteiro(B / 4)
Senão
C <- A
Fim_se
D <- C + 1.524
E <- Inteiro((D - 122,1) / 365,25)
F <- Inteiro(E * 365,25)
G <- Inteiro((D - F) / 30,6001)
H <- D - F - Inteiro(G * 30,6001)
Se (G < 14) Então
I <- G - 1
Senão
I <- G - 13
Fim_se
Se (I > 2) Então
J <- E - 4.716
Senão
J <- E - 4.715
Fim_se
Se (J > 0) Então
K <- J
Senão
K <- Módulo(J + 1) {Módulo = valor
positivo}
Fim_se
DIA <- H
MÊS <- I
ANO <- K
Escreva DIA, MÊS, ANO
O algoritmo em questão atribui a variável A o valor de dias
Juliano, e em seguida verifica se o valor de dias Juliano é
maior que 2299160 (no calendário Gregoriano, equivale a
04/10/1582). Se esta condição for verdadeira, são
executados os devidos ajustes de tempo para o valor da
variável C; caso contrário (sendo a data pertencente ao
calendário Juliano), a variável C é implicada directamente
pelo valor da variável A (sem os ajustes). Seguidamente, é
executada uma série de cálculos para extrair os valores do
DIA, MÊS e ANO do dia Juliano fornecido. Verificação de
Ano Bissexto Para verificar se o ano de uma data é ou não
bissexto deve-se fazer uso do algoritmo apresentado em
seguida.
Leia ANO
Se (ANO mod 4 <> 0) Então
<26>
a programar
ANO NÃO É BISSEXTO
Se (ANO mod 100 = 0) Então
Se (ANO mod 400 = 0) Então
Escreva “ANO É BISSEXTO”
Senão
Escreva “ANO NÃO É BISSEXTO”
Fim_se
um resultado sempre inteiro). Neste caso, existe a
possibilidade de o ano ser bissexto. Dentro dessa
possibilidade, é utilizada uma segunda condição (para
detectar os anos que terminam com 00), em que se verifica
se o ano é divisível por cem. Se o ano for divisível por cem, é
possível ainda ser bissexto. Para verificar esta possibilidade
utiliza-se a terceira condição quando o ano é dividido por
400.
Senão
Escreva “ANO É BISSEXTO”
Fim_se
Fim_se
Referência Bibliográfica
Com o algoritmo de ano bissexto é possível verificar
primeiro se uma data é divisível por quatro. Pode ser
divisível quando o resto da divisão do ano por quatro for
diferente de zero (considerando como quociente da divisão
MANZANO, J. A. N. G.; YAMATUMI, W. Y. Free Pascal:
Programação de Computadores. São Paulo, Brasil: Editora
Érica, 2007. 390 p., ISBN 978-85-365-0136-9.
Natural da Cidade de São Paulo, Augusto Manzano tem 23
anos de experiência em ensino e desenvolvimento de
programação de software. É professor da rede federal de
ensino no Brasil, no Centro Federal de Educação
Tecnológica de São Paulo. É também autor, possuindo na
sua carreira mais de cinquenta obras publicadas..
Augusto Manzano
<27>
<28>
segurança
Internet Protocol
Version 6 – IPv6
IPv6, também conhecido por IPng, é chamado por muitos de
protocolo da próxima geração. E visto que estamos cada vez
mais próximos do upgrade da versão 4 para a 6 convém
começarmos a familiarizar-nos com este protocolo e no que
traz de novo. Grande parte das pessoas não sabe que
estamos a chegar ao limite do IPv4. Na verdade, dos
teoricamente 4.3 mil milhões de endereços que o IPv4
permite apenas temos disponíveis cerca de 250 milhões
endereços. E ainda existem muitas áreas onde o IP pode e
vai ser aplicado. Além destas aplicações que se pretendem
fazer, também temos que levar em conta que apenas cerca
de 10% da população mundial está ligada à Internet e que
países como por exemplo a China e Índia ainda agora
começaram a ter acesso a ela. O IPv4 não consegue fazer
face a todas estas necessidades, daí a necessidade de haver
uma mudança. Muito se tem feito para tentar atenuar a
escassez de endereços com o IPv4, tem-se recorrido à
Variable Lenght Subnet Masks (VLSM) e ao Network
Address Protocol (NAT) para tentar aproveitar ao máximo
os endereços disponíveis. Mas o IPv6 não se trata apenas de
ter mais espaço, não, este protocolo sofreu uma
modificação radical tanto na sua estrutura como no seu
funcionamento. Foi melhorado significativamente para
oferecer mais capacidade, eficácia, flexibilidade e
optimização para ir ao encontro das necessidades actuais.
Benefícios e Usos do IPv6
mil milhões de endereços disponíveis, passamos a poder
usar cerca de 340.282.366.920.938.463.463.374.607.431.
768.211.456 de endereços. Mas existem outras
características que fazem com que uma mudança para o
IPv6 seja benéfica e que compense o tempo, o custo e o
esforço da mudança. Muitas das coisas que existem hoje
numa rede e em especial na Internet não foram pensadas
quando se desenvolveu o IPv4 e por isso tem-se recorrido a
muitos “remendos” para tentar colmatar essas falhas. Além
de que as tecnologias mais recentes começam apresentar
alguma dificuldade em trabalhar com o IPv4. Existem outros
benefícios que vão surgir com a utilização do IPv6:
• Auto-configuração;
• Segurança (IPsec);
• Melhor suporte para diferentes engenharias (diffserv ou
RSVP);
• Multicast;
• Melhor suporte para redes ad hoc;
E um dos melhores e grandes benefícios que o IPv6 vai
trazer é a eliminação do broadcast por completo.
Estrutura
Como foi referido anteriormente, o IPv6 apresenta
mudanças profundas na sua estrutura, em especial no seu
cabeçalho (header). Em primeiro lugar 5 campos do
cabeçalho no IPv4 foram removidos, Header Length,
Identification, Flags, Fragment Offset e Header Checksum.
O Header Length foi removido porque o header já tem um
tamanho fixo. Os campos Identification, Flags, e Fragment
Offset estão relacionados com a fragmentação de pacotes
em IPv4, mas foram removidos porque a fragmentação de
pacotes em IPv6 funciona de maneira diferente. O campo
Header Checksum também foi removido para aumentar a
velocidade de processamento. Isto porque se os routers não
precisarem de verificar e actualizar os checksums o
processamento torna-se mais rápido.
Talvez o que salte logo á vista seja o facto de ao invés dos 4
Fonte:
http://www.cisco.com/web/about/a
c123/ac147/archived_issues/ipj_93/ipv6_internals.html
<29>
segurança
Endereços e expressões no IPv6
Como é que o IPv6 funciona
É importante referir que os endereços no IPv6 vão ser muito
maiores tendo em conta que passamos a usar 128 bits. Por
isso em vez do habitual 192.168.10.10 passamos a ter algo
como 2001:0db8:3c4d:0012:0000:0000:1234:56ab. A
diferença que talvez mais se destaque seja o tamanho e o
facto de usarmos 8 conjuntos de números separados por um
“: “ e em hexadecimal. Também o endereço passa a ser
dividido em 3 partes:
Pode-se começar por explicar como um host recebe um
endereço IPv6 e como consegue encontrar recursos e outros
hosts numa rede. Também abordar o que é a stateless
autoconfiguration e stateful autoconfiguration.
• Global Prefix – 2001:0db8:3c4d
• Subnet – 0012
• Interface ID – 0000:0000:1234:56ab
De salientar que o endereço que estamos a usar como
exemplo pode ser resumido até ficar assim
2001:db8:3c4d:12::1234:56ab, mas isso já envolve algumas
regras que tem de ser seguidas, porque talvez tenham
reparado que eliminamos alguns zeros, mas isso nem
sempre é possível.
Endereços especiais
Assim como no IPv4 o IPv6 também endereços especiais e
que estão reservados para diversas funções: 0:0:0:0:0:0:0:0
(::.) – mesmo que 0.0.0.0 em Ipv4; 0:0:0:0:0:0:0:1 (::1) –
mesmo que o 127.0.0.1 em IPv4; Estes são apenas dois
exemplos de endereços reservados mais conhecidos, porque
muitos outros existem para se referir a tipos específicos de
endereços, como global unicast, unique local unicast, linklocal unicast, multicast.
Auto-configuração
Desde 1993 que se usa o DHCP em IPv4 de maneira a que os
host obtenham não só o endereço como também o Gateway
e o Sufixo DNS. No IPv6 existem, no entanto, duas maneiras
de configuração de um endereço. Uma é usando um
DHCPv6 server, que basicamente funciona como o DHCP
em IPv4 apesar de existir algumas diferenças nas mensagens
usadas, o conceito é o mesmo. A outra maneira é chamada
de stateless autoconfiguration. Com a auto-configuração
um host pode dinamicamente obter um endereço usando
para isso o endereço Link-Local Unicast. Para isto acontecer
o host precisa de um prefixo /64 para configurar a interface.
É então usado um protocolo do IPv6 chamado de Neighbor
Discovery Protocol (NDP). Assim o host envia aos routers
uma mensagem NDP chamada de RS (router solicitation)
usando para isso uma mensagem IPv6 multicast. Esta
mensagem pergunta qual é o prefix IPv6 usado nesta subnet
e qual é o endereço(s) usado(s) pelo(s) default router na
subnet.. O router por usa vez envia uma resposta com esse
prefixo através de um RA (router advertisement) usando
novamente o ICMP.
Tipos de Endereços
Novamente no IPv6, endereços tais como multicast, unicast
que existem no IPv4 e que certamente conhecemos e
estamos familiarizados, foram reaproveitados e usados
também no IPv6. No entanto um endereço foi
completamente eliminado, o broadcast. Lembram-se das
broadcast storms? Pois é, esse foi um dos muitos problemas
que foi eliminado/resolvido com a eliminação do broadcast e
que certamente não vai deixar saudades. Um endereço foi
eliminado e um foi criado, estou-me a referir é claro ao
Anycast, mas já vamos lá. Só de salientar que os endereços
Unicast, Global Unicast, Multicast, Link-Local e Unique
Local funcionam basicamente como no IPv4 e sem grandes
diferenças ou praticamente nenhumas no IPv6. Mas como
dissemos surgiu um novo tipo de endereço, o Anycast. Este
endereço identifica diversas interfaces, mas o pacote
anycast apenas é entregue a um endereço. Neste caso é
entregue ao primeiro endereço que “ele” encontre e que
corresponda aos termos definidos pelo routing da rede. Este
endereço é usado em conjunto com o protocolo BGP.
O host pega então o prefixo que recebeu e junta-o com o
seu interface ID (MAC) e no meio desse valor junta um FFFE
para compensar os bits que faltam. O processo é mais
complexo do que isto, porque é usado certo tipo específico
de mensagens do ICMP assim como é usado endereços
Multicast. Além de que existem diversas opções para a
configuração do endereço.
Migrar para o IPv6
Apesar de todas as vantagens apresentadas certamente
muitos se perguntem qual é que vai ser o “custo” e o
<30>
segurança
trabalho que vai dar esta migração. Não podemos pensar
que vai haver uma migração em massa do IPv4 para IPv6,
isto porque o número de dispositivos com IPv4 ascende aos
milhões e nem todos esses dispositivos suportam IPv6 por
isso é bem provável que se vá demorar uns bons anos a fazer
a transição. Além de que a transição vai depender também
da infra-estrutura que tenhamos, como óbvio se tivermos
routers ou switches já arcaicos o custo da migração vai ser
maior. Mas foram criadas algumas estratégias que
permitem uma integração gradual do IPv4 para a versão 6.
- NAT-PT
- IPv4/IPv6 Dual Stacking
Segurança
O termo Dual Stack significa que o host ou o router usa o
IPv4 e o IPv6 ao mesmo tempo. Para os hosts significa que
tem associado ao NIC tanto um endereço IPv4 como um
IPv6 e que consegue enviar pacotes IPv4 para hosts com o
IPv4 e o mesmo com o IPv6. Para os routers significa que
além dos endereços IPv4 e routing protocols também tem
configurado endereços IPv6 assim como os respectivos
routing protocols. Esta é a estratégia mais usada porque é a
mais fácil de se usar isto porque permite o upgrade gradual
tanto dos dispositivos como das aplicações.
6to4 Tunneling
É na segurança que o IPv6 também se destaca e pela
positiva. Nesta área viu-se a necessidade de redesenhar e
incorporar algumas características básicas em termos de
segurança, de maneira a que o IPv6 possa, pelo menos,
providenciar um nível mínimo de segurança contra as muitas
ameaças a que a Internet está sujeita. Por isso alguns
aspectos básicos foram levados em conta como a
integridade a confidencialidade e a autenticação. Como já
dissemos anteriormente, o IPv4 quando foi desenhado não
levou em conta as ameaças que iriam surgir. Por isso
surgiram algumas falhas de segurança, por exemplo com
alguns protocolos usados. Protocolos esses que “confiam” a
sua segurança da autenticação dos endereços IP e das
portas. No entanto não sabemos se determinado pacote IP
foi modificado ou lido o que leva a uma falha de segurança.
Além de que muitos protocolos que requerem autenticação
enviam a password em puro texto que facilmente poderá ser
descodificado. Estes são alguns dos problemas que são
colmatados através de Filtros para pacotes, Firewalls, SSL,
PEM, PGP, etc. Como é o IPv6 é mais seguro? Graças à
Framework IPSec, que é uma Framework de segurança para
a camada onde trabalha o IP. Esta Framework consiste em
seis elementos distintos: requerimentos de segurança na
Existem diversos tipos de tunneling, mas neste caso o que é
feito é pegar num pacote IPv6 e encapsula-lo dentro de um
pacote IPv4. Existem diversos tipos de tunneling que
podemos usar: Manually configured tunnels (MCT); Dynamic
6to4 tunnels; Intra-site Automatic Tunnel Addressing
Protocol (ISATAP) e; Teredo tunneling. Um aspecto a ter em
atenção é que se em algum ponto existir um NAT, este irá
“destruir” esse túnel, mas existe uma maneira de dar a volta
este problema e é por usarmos um método já citado –
Teredo.
Em alguns casos teremos que usar uma terceira opção que
permita que se converta um cabeçalho IPv6 em IPv4 e viceversa. Neste caso temos que usar o Network Address
Translation–Protocol Translation (NAT-PT) apesar de não
ser uma grande solução. Para que se dê esta conversão é
necessário que o router saiba quais os endereços IPv4 e IPv6
deve usar e assim como no NAT tradicional também temos
static definition, dynamic NAT, e dynamic PAT.
<31>
segurança
camada network, encriptação (ESP), autenticação (AH),
utilização de algoritmos criptográficos para encriptação e
autenticação, definição de políticas de segurança e gestão
da chave IPSec. Existem diversos usos que podemos dar á
IPSEC, confidencialidade nas transmissões, autenticação
dos peer para os updates de routing, server lookups e autoconfiguração em DHCP e também a prevenção de ataques
Denial-of-Service. O IPSEC também exerce a sua influência
em outros serviços e aplicações das diversas camadas, mas
nem todo é um mar de rosas porque existem algumas áreas
onde ainda ocorrem alguns conflitos, como por exemplo em
NAT, QoS e Tunneling. Mas também ainda existem falhas
no IPv6 apesar de se considerar o IPSEC estável, mas muito
trabalho ainda se tem de fazer em diversas áreas de maneira
a poder usufruir ao máximo aquilo que o IPv6 tem para
oferecer.
Conclusão
Neste pequeno artigo foram abordados diversos aspectos
relacionados com o IPv6, mas tudo isto que analisámos
apenas é um pequeno resumo de toda a informação
disponível. Para poderem aprofundar melhor este assunto
recomendo que leiam IPv6 Essentials O Reilly e o Chapter 13
- Internet Protocol Version 6 (IPv6) do Sybex CCNA Cisco
Certified Network Associate Study Guide Exam 640-802 6th
Edition. Podem também consultar o site
http://www.pt.ipv6tf.org/ .
Residente em Lisboa, Ciro Cardoso é um grande amante do
vasto mundo que é a informática em especial a segurança.
Está actualmente a trabalhar na próxima etapa do percurso
Cisco com a certificação CCNP. Gosta bastante da área de
redes, configuração de protocolos assim como a
implementação.
Ciro Cardoso
<32>
internet
USACO Training Pages
Site de treino das olimpíadas de informática dos EUA, recomendado a qualquer programador com
interesse em testar/treinar as suas capacidades em algoritimia. Com background teórico e novos
problemas desbloqueáveis à medida que se avança, o site inclui ainda um grader que nos permite saber
em que testes o programa submetido errou, entre outras informações.
http://train.usaco.org/
Android
Site do projecto Android da Open Handset Alliance, uma plataforma opensource para telemóveis em que
o Google participa, onde poderá encontrar o SDK e boa documentação sobre o Android.
http://code.google.com/android/
Free Software Foundation Licenses
Uma página com licenças reconhecidas pela FSF que permitem a distribuição de software e outro tipo de
conteúdos de forma free as in speech. Muito útil se planeia lançar software opensource e quer saber qual a
que melhor se adequa a si e ao público-alvo do seu programa/biblioteca.
http://www.fsf.org/licensing/licenses/
PHP Classes Repository
Este site é um dos maiores repositórios de código PHP, nele pode encontrar milhares de classes que o vão
ajudar no desenvolvimento das suas aplicações.
http://www.phpclasses.org/
<33>
Queres participar na Revista
PROGRAMAR? Queres integrar este
projecto, escrever artigos e ajudar a
tornar esta revista num marco da
programação nacional?
Vai a
www.revista-programar.info
para mais informações em como
participar,
ou então contacta-nos por
revistaprogramar
@portugal-a-programar.org
Precisamos do apoio de todos para
tornar este projecto ainda maior...
contamos com a tua ajuda!
Equipa PROGRAMAR
Um projecto Portugal-a-Programar.org