Olhando para um documento XML, o aspeto geral é semelhante ao duma página Web (documento HTML). No entanto há importantes diferenças entre os dois formatos:
Em todo o caso, o formato HTML é um caso particular do formato XML, se esquecermos o detalhe do formato HTML ser um pouco menos rígido sintaticamente.
Para exemplificar, eis um pequeno documento XML com alguma informação sobre uma senhora chamada Alice e os seus filhos:
<person gender='F'> Alice <children> <person gender='M'> Andre <children></children> 5 years old </person> <person gender='F'> Maria <children></children> 4 years old </person> </children> 35 years old </person>Neste exemplo são usadas apenas duas marcas: <person> e <children>. Quando as marcas são bem escolhidas, o documento XML torna-se autoexplicativo. Você deverá conseguir responder a estas perguntas: Qual a idade de Alice? Quantos filhos tem? Qual o nome e idade dos filhos? Ela tem algum neto?
De forma geral, um documento XML tem a estrutura duma árvore n-ária. O texto correspondente à árvore completa ou qualquer das suas subárvores chama-se um elemento XML.
Sintaxe:
Um elemento sem conteúdo, como <children></children> pode ser abreviado <children/>.
Observemos agora um documento XML grande, contendo uma famosa comédia escrita por William Shakespeare: A Midsummer Night's Dream.
Style Sheets: São mecanismos de especificação da forma como documentos XML devem ser visualizados. Estão disponíveis dois desses mecanismos: XSL - XML Style Sheets e CSS - Cascading Style Sheet. O primeiro mecanismo é o mais poderoso, e permite especificar transformações de XML em XML ou HTML (a sintaxe da linguagem de transformação é, ela própria, baseada em XML e usa o namespace predefinido "xsl"; os nós da árvore a serem tratados especificam-se usando uma linguagem de expressões chamada XPath). O segundo mecanismo permite associar a cada tipo de elemento informação de formatação, e.g. fonte, cor, margem, etc.
Eis um pequeno exemplo que ilustra tudo o que é preciso saber se quisermos processar um documento XML sem o alterar:
Código do exemplo:
<SCRIPT TYPE="text/javascript"> function showXML(x) { let str = "" str += "node tag = " + x.nodeName + "\n"; str += "parent name = " + x.parentNode.nodeName + "\n"; str += "number of children = " + x.childNodes.length + "\n"; str += "tags of the children:" + "\n"; for( let i = 0; i < x.childNodes.length ; i++ ) { if( x.childNodes[i].nodeName == "#text" ) str += " " + x.childNodes[i].nodeName + " (" + x.childNodes[i].textContent + ")\n"; else str += " " + x.childNodes[i].nodeName + "\n"; } str += "number of attributes = " + x.attributes.length + "\n"; str += "names and values of the attributes:" + "\n"; for( let i = 0; i < x.attributes.length ; i++ ) { str += " " + x.attributes[i].nodeName + " = " + x.attributes[i].textContent + "\n"; } alert(str); } function people(x) { let nodes = x.getElementsByTagName("person"); for( let i = 0 ; i < nodes.length ; i++ ) showXML(nodes[i]); } function xml() { let parser = new DOMParser(); // let serializer = new XMLSerializer(); let text = `<person gender='F'> Alice <children> <person gender='M'> Andre <children></children> 5 years old </person> <person gender='F'> Maria <children></children> 4 years old </person> </children> 35 years old </person>`; let xmlDoc = parser.parseFromString(text,"text/xml"); people(xmlDoc); } </SCRIPT> <INPUT TYPE="button" VALUE="Click Me" ONCLICK="xml()"> |
O formato JSON é independente de qualquer linguagem de programação, mas usa as mesmas convenções sintáticas dos objetos literais do JavaScript. Cada objeto JSON é constituido por uma coleção de pares etiqueta/valor e as sequências são representadas por arrays.
Eis um exemplo em XML
<person gender='F'> <name>Alice</name> <age>35</age> <children> <person gender='M'></person> <person gender='F'></person>< /children> </person>e a respetiva tradução em JSON, feita neste site:
{ "person": { "-gender": "F", "name": "Alice", "age": "35", "children": { "person": [ { "-gender": "M" }, { "-gender": "F" } ] } } }
[Esta tradução está correta. Repare que não estamos a traduzir a "Alice" original, mas sim uma segunda versão simplificada.]
O sistema de base de dados MongoDB guarda documentos em formato JSON, permitindo indexar por quaisquer campos, mesmo que estejam muito aninhados no interior de subobjetos.
A naturalidade da modelação dos dados e o seu poder de indexação têm tornado o MongoDB bastante popular (está em 5º lugar, atrás de concorrentes bem mais poderosos).
O MongoDB é um "document-oriented database system" e a facilidade e informalidade da sua utilização é bem-vinda em muitas aplicações Web. Contudo, se a base de dados tiver uma grande quantidade de relações e de normalização, então deverá ser preferivel usar uma base de dados relacional clássica.
Um desenvolvimento relativamente recente, foi o aparecimento do Node.js. Trata-se dum interpretador de JavaScript que vem com uma biblioteca que o torna apto a ser usado do lado do servidor. Foi criado em 2009 por Ryan Dahl. Tem tido uma adoção rápida pela comunidade da programação para a Web.
Baseia-se na ideia de usar o modelo de eventos, já conhecido do lado do cliente, também do lado do servidor. Cada pedido que chega é considerado um evento e é tratado assincronamente. Tal como no caso do cliente, existe uma thread dos eventos que executa continuamente um ciclo que retira e processa os eventos guardados numa fila de espera. Os eventos são tratados sequencialmente e enquanto não terminar o tratamento dum evento não se inicia o tratamento do seguinte.
Apetece criticar o uso do modelo de eventos do lado do servidor argumentando que o tratamento dum pedido pode ser demorado - realmente pode envolver a consulta duma base de dados ou o carregamento dum ficheiro grande, por exemplo. Mas isso não é problema no caso do Node.js porque ele tem o cuidado de lançar em threads auxiliares as operações de biblioteca mais demoradas, gastando pouco tempo na thread dos eventos. Por outras palavras, muitas das operações da biblioteca do Node.js funcionam de forma assíncrona. As threads auxiliares, ao terminarem, geram elas mesmo eventos de finalização que permitem ao programador aceder ao resultado da operação e prosseguir com mais ações. No exemplo abaixo, o método de biblioteca readFile faz o carregamento integral do ficheiro cujo nome é passado no primeiro argumento. O últmo argumento de readFile é a função que fará o tratamento do reaultado da leitura quando o evento de finalização ocorrer.
Como já se disse na aula anterior, o uso de lógica assíncrona tem consequências dramáticas no estilo de programação! Nos programas clássicos, o programa consegue controlar todo o fluxo de execução de forma estrita. Na programação assíncrona, escreve-se código para reagir a eventos e não há possibilidade de controlar o fluxo de execução geral. Vamos ver um exemplo.
<FORM NAME="form1" ACTION="http://localhost:8080"> Get file: <INPUT NAME="q" VALUE="aaa"> <INPUT TYPE="submit"> </FORM> |
nodejs fileserver.js |
Agora o código do servidor. Note que o servidor HTTP é criado no último bloco de código e ativado usando o método listen. Repare também que há duas funções que funcionam assincronamente: fs.exists e fs.readFile.
// fileserver.js let http = require("http"); let path = require("path"); let url = require("url"); let fs = require("fs"); function handleError(error, response) { console.log("ERROR"); response.writeHeader(500, {"Content-Type": "text/plain"}); response.write(error + "\n"); response.end(); } function handleNotFound(response) { console.log("FILE NOT FOUND"); response.writeHeader(404, {"Content-Type": "text/plain"}); response.write("404 Not Found\n"); response.end(); } function handleIgnore() { console.log("IGNORE"); } function handleReply(file, response) { console.log("OK"); response.writeHeader(200); response.write(file, "binary"); response.end(); } let server = http.createServer(function(request, response) { console.log("Request " + request.url); let urlParsed = url.parse(request.url, true); if(urlParsed.query.q == undefined) { handleIgnore(); return; } let localPath = urlParsed.query.q; let fullPath = path.join(process.cwd(),localPath); console.log("FullPath " + fullPath); fs.exists(fullPath, function(exists) { if(!exists) handleNotFound(response); else fs.readFile(fullPath, "binary", function(error, file) { if(error) handleError(error, response); else handleReply(file, response); }); }); }); server.listen(8080); console.log("Fileserver is running on port 8080"); |
De todas estas variantes, os frameworks possuem a curva de aprendizagem mais longa. O investimento de tempo tem de ser razoavelmente grande. Não admira: é preciso conhecer todas as abstrações usadas pelo criador do framework e o nosso código tem de se submeter a essas abstrações, implementando especializações delas. Contudo, depois de aprendido o framework, há vantagens no nível de produtividade e na qualidade das aplicações escritas.
Para começar, uma descrição rápida do site pretendido. O site deve permitir ao utilizador aceder a diversos tipos de informação, nas categorias "Blog", "Services", "Carrers", "Contacts". Deve também haver um painel de controlo protegido por password, para o administrador poder introduzir nova informação. É tudo.
Note que a forma essencial de usar diretamente o Node.js já foi apresentada atrás. No entanto, dentro dum framework o uso do Node.js é indireto: em muitas situações, trabalha-se a um nível de abstração mais elevado e parte da tarefa de programação consistem em configurar coisas, por exemplo especificando qual o código que deve correr em resposta a um pedido envolvendo um URL particular.
Assumimos que estamos a trabalhar num sistema Linux com suporte para o sistema de instalação de pacotes apt, por exemplo Ubuntu ou Debian.
Passo 1 - Garantir que se tem a última versão do Node.js instalada e atualizar o gestor de pacotes npm do JavaScript.
# curl -sL https://deb.nodesource.com/setup_0.12 | sudo -E bash - ... # sudo apt-get install -y nodejs ... # node -v v0.12.14 # sudo npm install npm -g ... # npm -v 3.9.0
Passo 2 - Instalar o sistema de comunicações seguras Kerberos, o sistema de base de dados MongoDB, o framework Express.js e o o sistema de testes unitários jasmine-node. Note que o MongoDB é grande, ficando a ocupar cerca de 1GB no disco.
# sudo apt-get install libkrb5-dev mongodb ... # sudo npm install express-generator -g ... # sudo npm install jasmine-node -g ...
Passo 3 - Descarregar e instalar a aplicação já escrita:
# wget https://github.com/tutsplus/build-complete-website-expressjs/archive/master.zip ... # unzip master.zip ... # cd build-complete-website-expressjs-master/app ... # npm install # descarrega e instala todas as bibliotecas de que a aplicação depende ... # jasmine-node ./tests # corre todos os testes unitários para validar instalação. ...
Passo 4 - Lançar a aplicação. Numa consola fazer:
# cd build-complete-website-expressjs-master/app ... # node app.js Successfully connected to mongodb://127.0.0.1:27017 Express server listening on port 3000 GET / 200 1244ms - 2.54kb GET /images/logo.png 304 158ms GET /stylesheets/style.css 200 851ms - 3.25kb GET /images/nav-bg.jpg 304 1ms GET /images/home-title.jpg 304 1ms GET /blog 200 29ms - 1.67kb GET /images/logo.png 304 1ms GET /stylesheets/style.css 304 3ms GET /images/nav-bg.jpg 304 1ms GET /services 200 7ms - 1.3kb GET /images/logo.png 304 1ms GET /stylesheets/style.css 304 4ms GET /careers 200 5ms - 1.3kb GET /stylesheets/style.css 304 1ms GET /images/logo.png 304 1ms GET /contacts 200 3ms - 1.3kb GET /images/logo.png 304 0ms GET /stylesheets/style.css 304 2ms ...
Passo 5 - Para usar a aplicação de forma normal:
# google-chrome http://localhost:3000 ...Para usar a aplicação em modeo de administrador:
# google-chrome http://localhost:3000/admin ...Para aceder ao painel de diagnósticos do MongoDB:
# google-chrome http://localhost:28017 ...
O padrão arquitectural MVC decompõe uma aplicação em três partes: (1) gestão dos dados, (2) lógica e (3) interface com o utilizador. Esta decomposição ajuda a tornar o programa mais compreensível e mais fácil de manter a longo prazo.
Eis alguma informação sobre os Modelos, Controladores e Visualizadores da nossa aplicação:
Na nossa aplicação, para simplificar, todos os dados são do mesmo tipo e são guardados na mesma coleção. Cada registo tem os seguintes campos: title, text, picture, type. O campo type determina que é o controlador responsável pelo registo. Portanto temos apenas um modelo, a que foi dado o nome de ContentModel.
Na nossa aplicação há vários controladores: Admin, Blog, Home, Page. São os controladores que decidem como é que a aplicação responde a pedidos HTTP e para isso usam-se as funcionalidades de routing do Express.js.
Na nossa aplicação só está definido um visualizador chamado Base. A parte de visualização ainda está pouco desenvolvida e será possível, no futuro, criar subclasses deste visualizador. O nosso visualizador produz HTML usando a primitiva de render do Express.js.
# express --sessions --css less --hogan app create : app create : app/package.json create : app/app.js create : app/public create : app/public/images create : app/public/stylesheets create : app/public/stylesheets/style.less create : app/routes create : app/routes/index.js create : app/routes/users.js create : app/views create : app/views/index.hjs create : app/views/error.hjs create : app/bin create : app/bin/www install dependencies: $ cd app && npm install run the app: $ DEBUG=app:* npm start create : app/public/javascriptsDepois Krasimir instalou as biblitecas de que a aplicação depende usando o comando:
# npm install app@0.0.0 /media/big/amd2/xtra-ubuntu-14.04/home/amd/aa/app/app +-- body-parser@1.13.3 | +-- bytes@2.1.0 | +-- content-type@1.0.2 | +-- depd@1.0.1 | +-- http-errors@1.3.1 | | +-- inherits@2.0.1 | | `-- statuses@1.3.0 | +-- iconv-lite@0.4.11 | +-- on-finished@2.3.0 | | `-- ee-first@1.1.1 | +-- qs@4.0.0 | +-- raw-body@2.1.6 | | +-- bytes@2.3.0 | | +-- iconv-lite@0.4.13 | | `-- unpipe@1.0.0 | `-- type-is@1.6.13 | +-- media-typer@0.3.0 | `-- mime-types@2.1.11 | `-- mime-db@1.23.0 +-- cookie-parser@1.3.5 | +-- cookie@0.1.3 | `-- cookie-signature@1.0.6 +-- debug@2.2.0 | `-- ms@0.7.1 +-- express@4.13.4 | +-- accepts@1.2.13 | | `-- negotiator@0.5.3 | +-- array-flatten@1.1.1 | +-- content-disposition@0.5.1 | +-- cookie@0.1.5 | +-- depd@1.1.0 | +-- escape-html@1.0.3 | +-- etag@1.7.0 | +-- finalhandler@0.4.1 | +-- fresh@0.3.0 | +-- merge-descriptors@1.0.1 | +-- methods@1.1.2 | +-- parseurl@1.3.1 | +-- path-to-regexp@0.1.7 | +-- proxy-addr@1.0.10 | | +-- forwarded@0.1.0 | | `-- ipaddr.js@1.0.5 | +-- range-parser@1.0.3 | +-- send@0.13.1 | | +-- depd@1.1.0 | | +-- destroy@1.0.4 | | +-- mime@1.3.4 | | `-- statuses@1.2.1 | +-- serve-static@1.10.2 | +-- utils-merge@1.0.0 | `-- vary@1.0.1 +-- hjs@0.0.6 | `-- hogan.js@3.0.2 | +-- mkdirp@0.3.0 | `-- nopt@1.0.10 | `-- abbrev@1.0.7 +-- less-middleware@1.0.4 | +-- less@1.7.5 | | +-- clean-css@2.2.23 | | | `-- commander@2.2.0 | | +-- graceful-fs@3.0.8 | | +-- mime@1.2.11 | | +-- mkdirp@0.5.1 | | | `-- minimist@0.0.8 | | +-- request@2.40.0 | | | +-- aws-sign2@0.5.0 | | | +-- forever-agent@0.5.2 | | | +-- form-data@0.1.4 | | | | +-- async@0.9.2 | | | | +-- combined-stream@0.0.7 | | | | | `-- delayed-stream@0.0.5 | | | | `-- mime@1.2.11 | | | +-- hawk@1.1.1 | | | | +-- boom@0.4.2 | | | | +-- cryptiles@0.2.2 | | | | +-- hoek@0.9.1 | | | | `-- sntp@0.2.4 | | | +-- http-signature@0.10.1 | | | | +-- asn1@0.1.11 | | | | +-- assert-plus@0.1.5 | | | | `-- ctype@0.5.3 | | | +-- json-stringify-safe@5.0.1 | | | +-- mime-types@1.0.2 | | | +-- node-uuid@1.4.7 | | | +-- oauth-sign@0.3.0 | | | +-- qs@1.0.2 | | | +-- stringstream@0.0.5 | | | +-- tough-cookie@2.2.2 | | | `-- tunnel-agent@0.4.3 | | `-- source-map@0.1.43 | | `-- amdefine@1.0.0 | +-- mkdirp@0.3.5 | `-- node.extend@1.0.10 | `-- is@0.3.0 +-- morgan@1.6.1 | +-- basic-auth@1.0.4 | `-- on-headers@1.0.1 `-- serve-favicon@2.3.0O conteúdo do ficheiro /package.json ficou o seguinte:
{ "name": "app", "version": "0.0.0", "private": true, "scripts": { "start": "node ./bin/www" }, "dependencies": { "body-parser": "~1.13.2", "cookie-parser": "~1.3.5", "debug": "~2.2.0", "express": "~4.13.1", "hjs": "~0.0.6", "less-middleware": "1.0.x", "morgan": "~1.6.1", "serve-favicon": "~2.3.0" } }Para correr a aplicação vazia, fazer:
# npm starte o seguinte link - http://localhost:3000 - permite ver o que a aplicação vazia faz.
Para aprender mais sobre este exemplo, agora só mesmo estudando o tutorial, consultando ocasionalmente a documentação da API do Express.js.