Categoría: Computación
28 Enero 2009
Acabo de subir a Github clj-haml, un apaño para tener algo parecido a Haml (http://haml.hamptoncatlin.com/) para clojure.

Haml:
%div#things
%span#rice Chicken Fried
%p.beans{ :food => 'true' } The magical fruit
%h1.class.otherclass#id La La La
clj-haml:
(h= :div#things
(h= :span#rice "Chicken Fried")
(h= :p.beans {:food "true"} "The magical fruit")
(h= :h1.class.otherclass#id "La La La"))
El HTML generado en ambos casos:
<div id='things'>
<span id='rice'>Chicken Fried</span>
<p class='beans' food='true'>The magical fruit</p>
<h1 id='id' class='class otherclass'>La La La</h1>
</div>
Sólo hay una función:
(h= :selector attributes contents /)
Todos los parámetros son opcionales, :selector es un keyword que puede incluir el nombre de una etiqueta HTML seguido de un id y algunas clases especificadas siguiendo la notación CSS: :p#myid.class1.clas2, :.class1#myid, :br
Attributes es un mapa asociativo con pares clave valor para nombre de atributo y valor, por ejemplo: {:href "/test.html"}.
Contents son unos cuantos forms, potencialmente conteniendo otras llamadas (h=) que se evaluarán y cuyo valor devuelto se insertará en la etiqueta que se está generando.
Por último, el / final opcional, si presente, hace que la etiqueta generada sea con autocierre (h= :br /) -> <br/>, (h= :br) -> <br></br>.
Como en Haml vamos.
La podéis descargar aquí (http://github.com/antoniogarrote/clj-haml/tree/master).
servido por Antonio
sin comentarios
compártelo
24 Enero 2009
En las últimas semanas, mi pequeña biblioteca clojure para grizzly ha ido creciendo y ya puede aceptar cualquier petición web y renderizar una respuesta.
Desde el punto de vista de cliente, el proceso es ahora mismo este:
Enrutamos una petición a una función que procesará la petición:
(route! (url-pattern GET "greetings" :name)
say-hello)
Con la anterior expresión estamos enrutando las peticiones "GET /greetings/PARAMETRO" a la función say-hello, que además recibirá el valor de PARAMETRO con el nombre :name.
Definamos ahora la función say-hello:
(defn say-hello
([] (render (if (= (parameter :name) "Helena")
"Hola chica guapa!"
(str "Hola " (parameter :name)))
200
{:Content-Type "text/plain"})))
En esta función básicamente decimos que queremos renderizar un mensaje u otro en función del valor del parámetro :name de la URL.
Hasta aquí todo sencillo, pero lo interesante es que hay debajo de todo esto ¿qué hacen las llamadas a render y parameter? Bien, la respuesta es que toda la lógica de servicio de una petición se encuentra encerrado dentro de una mónada, la Mónada WebIO. Esta mónada se define como:
;;
;; WebIO X = WebIOUnfinished X |
;; WebIOFinished X
;; WebIOtFailed
;;
(defstruct web-request-processing :environment ;; The Rack request
:response ;; The response of the request
:parameters) ;; the parameters hash
La petición, la respuesta y los parámetros se guardan en la mónada que puede tener tres estados pendiente, terminada o fallida que establecen el estado de la respuesta.
La función de binding para la mónada WebIO, define como se deben combinar funciones monádicas según el resultado de aplicar la petición contenida en la mónda:
(defmethod >>= :WebIO [f m]
"Instance of the >>= function for the WebIO monad. If the computation
result of m is WebIO Unfinished, the next function f is applied to the
content of the m. If the result of m is WebIO Failed, next function
f is not applied and m is returned. If the result of m is WebIO
Finished, f is not invoked"
(if (or (web-io-failed? m) (web-io-finished? m))
m
(f (:content m))))
Para poder realizar el procesamiento de la petición sólo se necesitan dos cosas una función que transforme una petición web, a través de la interfaz Rack que usamos, en una mónada WebIO y algún método para transformar una función normal como la anterior usada como ejemplo (say-hello), que no sabe nada sobre mónada alguna en una función monádica capaz de ser procesada por el operador de binding (>>=).
De lo primero se encarga la función (wrap-request) que usa la función general monádica (return) para envolver una petición Rack, además de protegerla con una referencia para poder ser usada facilmente mediante el mecanismo de memoria transaccional software (STM) que implementa Clojure:
(defn wrap-request
"Wrapper around return that builds the new request monad initializing it
with the values of the rack-request"
{:monad :WebIO}
([rack-request rack-response parameters subtype]
(return :WebIO subtype
(struct web-request-processing
rack-request
(if (= (class rack-response) clojure.lang.Ref)
rack-response
(ref rack-response))
parameters)))
([rack-request]
(wrap-request rack-request (create-rack-response) {} :Unfinished))
([rack-request rack-response]
(wrap-request rack-request rack-response {} :Unfinished))
([rack-request rack-response parameters]
(wrap-request rack-request rack-response parameters :Unfinished)))
El segúndo requisito, transformar una función normal en una función monádica, lo realiza la función (with-web-io) y se conoce normalmente como "lifting". En este caso además, define unas variables accesibles sólo por el hilo que ejecuta esa función, para la petición, respuesta y parámetros, de forma que no sea necesario para el programador que implementa la función con la lógica de procesamiento, tener que pasar explícitamente estos parámetros a funciones como (parameter) o (render):
(defn with-web-io
"Lifts a call to a function for a web-io-processing struct into the web-io monad"
{:monad :WebIO}
([f web-io-processing]
(try
(let [result (binding [*request* (:environment web-io-processing)
*response* (:response web-io-processing)
*parameters* (:parameters web-io-processing)]
(apply f []))]
(if (web-io-monad? result)
result
(wrap-request (:environment web-io-processing)
(:response web-io-processing)
(:parameters web-io-processing)
:Unfinished)))
(catch Exception ex (raise (:environment web-io-processing)
500
(. ex getMessage))))))
Sobre estas funciones, el mecanismo que sirve una petición resulta trivial, el proceso cuenta con los siguientes pasos:
- Recibir la petición Rack
- Extraer la ruta y método de la petición y comprobar si se puede enrutar a alguna función de respuesta
- Transformar la petición Rack en una mónada WebIO
- Crear una colección con los filtros anteriores, la función de respuesta, y los filtros posteriores a los que se va a enrutar la petición
- Hacer un lift de cada una de estas funciones a funciones monádicas a través de (with-web-io)
- Ejecutar la secuencia de funciones monádicas con el operador de binding (>>=)
- Devolver el resultado a través de Rack
servido por Antonio
1 comentario
compártelo
12 Enero 2009
Clojure es un lenguaje de programación pensado por y para la concurrencia. Las características funcionales del lenguaje, como la inmutabilidad de sus estructuras de datos, contribuyen a que mantener varios hilos de ejecución sea sencillo, cada uno con su propia copia inmutable de los datos, ya que de esta manera, es imposible que un hilo modifique el estado de la computación de otro hilo.
Sin embargo, hay veces que es necesario compartir el estado entre varios hilos o, dado que Clojure se ejecuta sobre la máquina virtual de Java y es posible acceder a todos los objetos y clases de Java, trabajamos con estructuras de datos no inmutables.
Clojure ofrece diferentes formas para tratar estas situaciones automáticamente, de forma que se eliminen los problemas de concurrencia que aparecen en estas circunstancias, los agentes son una de ellas.
Un agente es una abstracción que mantiene el estado de una computación, actualizándolo mediante la ejecución de forma asíncrona de funciones sobre ese estado que son enviadas por otros hilos mediante la función (send). Estas funciones se van almacenando en una estructura FIFO y van siendo aplicadas secuencialmente por el agente en su propio pool de hilos.
Si habéis trabajado con Erlang, podéis pensar en los agentes Clojure como una versión recortada de los servidores Erlang.
Un agente clojure se declara con la función (agent):
(agent initial-state)
Donde initial-state puede ser cualquier estructura de datos. Por ejemplo, vamos a construir un logger asíncrono y thread-safe. Nuestro logger mantendrá pues un nivel de logging y un sitio donde escribir el log, por defecto le daremos el nivel "1" y la salida estándar:
(def *logger* (agent {:level 1 :output *out*}))
Para loguear los mensajes, necesitamos una función (log), que recibirá un nivel de logging y algo que loguear, como usar números para los niveles de log queda algo feo, definamos una función que transforma claves para los niveles de log en enteros:
(defn level-name-to-int
"Returns a numeric identifier for the levels of the logger"
([level]
(let [levels {:debug 0
:info 1
:warning 2
:error 3
:critical 4}]
(let [to-return (level levels)]
(if (nil? to-return) 6 to-return)))))
Ahora que tenemos niveles de log, necesitamos alguna forma de cambiar el nivel de logging actual almacenado en el agente del logger. Como ya mencionamos, para cambiar el estado de un agente, necesitamos una función que será enviada al agente para que la ejecute en su pool de hilos sobre el estado actual almacenado. La función de actualización debe recibir como primer argumento el estado del agente, una serie de parámetros opcionales y devolver un estado del agente modificado. Usando notación Haskell:
updateFunction :: AgentState -> [Args] -> AgentState
En nuestro caso, nuestra función para modificar el nivel de logging del agente puede ser:
(defn reset-logger-level
"Initializes the logger with a given level"
([level]
(send *logger* (fn [a l] {:level (level-name-to-int l) :output (:output a)}) level)))
Como se puede ver, enviamos al agente *logger* una función lambda, donde se transforma la estructura de dats almacenada en el agente, sustituyendo el viejo nivel de logeo con el suministrado en el argumento "level".
Ahora, necesitamos la función de logueo. Crearemos dos versiones, una que recibe un nivel y el código a loguear, y si el nivel del agente logger es adecuado loguea el resultado del código pasado a la función de log y lo devuelve tal cual. La segunda, recibirá además una descripción que logueará junto al resultado de ejecutar el códigos suministrado como argumento:
(defn log
"logs something in the logger with the provided level of log"
([level to-log]
(do
(send *logger* (fn [a level msg]
(do
(when (<= (:level a)
(level-name-to-int level))
(let [to-write (str (. (keyword-to-string level) toUpperCase) " "
(. (new java.util.Date) toString) " => "(str msg) "\n")
writer (:output a)]
(do
(. writer (write to-write 0 (. to-write length)))
(. writer flush))))
a))
level to-log)
to-log))
([level desc to-log]
(do
(send *logger* (fn [a level msg]
(do
(when (<= (:level a)
(level-name-to-int level))
(let [to-write (str (. (keyword-to-string level) toUpperCase) " "
(. (new java.util.Date) toString) " => " desc " " (str msg) "\n")
writer (:output a)]
(do
(. writer (write to-write 0 (. to-write length)))
(. writer flush))))
a))
level to-log)
to-log)))
En este caso, como en el anterior, enviamos una función lambda al agente con send para que haga el trabajo, esta función loguea, el resultado de la ejecución comprobando el nivel de log y añadiendo alguna información extra como la hora.
Es interesante notar que la función log no es bloqueante, en cuanto se ha enviado la función lambda al agente, retorna y la ejecución de la computación continúa, se haya logueado el mensaje o no, además como la ejecución de funciones en el pool del agente es FIFO, los mensajes se van a loguear en el orden de ejecución, sin que se "cuelen" mensajes en el orden de ejecución (la función send es atómica).
Aquí se puede comprobar como funciona en ejecución nuestro flamante logger:
user=> (reset-logger-level :debug)
(reset-logger-level :debug)
#=(clojure.lang.Agent. "clojure.lang.Agent@219009")
user=> (log :info "hola?" (+ 1 2))
(log :info "hola?" (+ 1 2))
3
user=> INFO Mon Jan 12 00:25:54 CET 2009 => hola? 3
(reset-logger-level :error)
(reset-logger-level :error)
#=(clojure.lang.Agent. "clojure.lang.Agent@219009")
user=> (log :info "ahora no... " (* 3 3))
(log :info "ahora no... " (* 3 3))
9
user=>
Como se puede ver, se puede meter llamadas a log en medio de la ejecución del código ya que es transparente para una llamada a función, además se puede comprobarla ejecución asíncrona del logger, comprobando como se cuela el logueo de la llamada para (log :info "hola?" (+ 1 2)) antes de que la shell de clojure muestre el cursor para la siguiente entrada de datos por parte del usuario.
Para terminar, vamos a hacer que en vez de loguear en la salida estándar, mande el log a un fichero. Como ya hemos dicho, Clojure es 100% java, y la variable especial de salida estándar *out*, no es más que un objeto java.io.OutputStreamWriter, así que vamos a hacer un par de funciones, una para cambiar el destino de logueo almacenado en el estado del agente, y otro para poder iniciarlo con un fichero de salida:
(defn reset-logger
"Initializes the logger with a given level and output writer"
([level writer]
(send *logger* (fn [a l w] {:level (level-name-to-int l) :output w}) level writer)))
(defn reset-logger-with-file-output
"Resets the logger setting the output to the file whose path is provided as an argument
with the level specified"
([level file-path]
(reset-logger level (new FileWriter (new File file-path) true))))
Ya tenemos terminado nuestro logger, de ultimísima tecnología, con el que no tendremos problemas como el que te puedes encontrar en Merb, cuando el tiempo que tarda en mostrar el log de tu aplicación web acaba provocando un timeout por parte del cliente. ;)
Por último, queda comentar sobre los agentes que les ocurre cuando la función de actualización lanza una excepción: intentemos loguear en null, algo que seguro no es una buena idea:
(send *logger* (fn [_] {:output nil :level 1}))
(send *logger* (fn [_] {:output nil :level 1}))
#=(clojure.lang.Agent. "clojure.lang.Agent@219009")
user=>
(log :error "????")
"????"
El mensaje no ha sido loguead, como esperábamos, pero tampoco parece que haya explotado nada, intentemos loguear algo más
user=> (log :error "are you alive?")
(log :error "are you alive?")
java.lang.Exception: Agent has errors (NO_SOURCE_FILE:0)
user=>
Vaya, parece que nuestro agente se ha quedado en un estado de error. Este es el comportamiento de los agentes Clojure ante los errores, se quedan suspendidos hasta que reseteemos el estado del mismo con la función (clear-agent-errors)
user=> (clear-agent-errors *logger*)
(clear-agent-errors *logger*)
nil
¡Listo para usar otra vez!
El código, como siempre, lo podéis descargar de Github
servido por Antonio
sin comentarios
compártelo
8 Enero 2009
Más cosas que voy implementando en mi biblioteca OWL para Clojure.
En otro post, mostré como se podía definir la TBox, con un procedimiento similar al de usar migraciones para definir el esquema de una base de datos relacional. Ahora vamos mostrar lo que sería el equivalente ontológico a insertar filas en las tablas del esquema relacional, es decir a insertar aserciones en la ABox.
Creamos una TBox de prueba:
(describe-tbox
(describe-owl-class :test "Person")
(describe-owl-datatype-property :test "name" (xsd-string))
(describe-owl-datatype-property :test "age" (xsd-decimal))
(describe-owl-class-has-property :test "Person" :test "name")
(describe-owl-class-has-property :test "Person" :test "age"))
Una vez guardada la TBox en un repositorio e iniciada (con alias :test-Person, :test-name y :test-age para los recursos), podremos crear individuos en la ABox que sean tipos de las clases OWL contenidas en la TBox, para ello usamos la función (abox-create-individual)
(abox-create-individual :test-Person
"http://test.com/abox/people#"
{ :test-name "Helena",
:test-age 24 })
Esta función crea un nuevo individuo con URI "http://test.com/abox/people#IDENTIFIER", donde IDENTIFIER es un identificador universal único (UUID) que se genera con la llamada a la función. La función inserta los tripletes RDF necesarios para asertar que el individuo recién creado es del tipo :test-Person, así como de todas sus superclases (en este caso owl:Thing). Además da valores a las propiedaded test:name y test:age.
Los tripletes RDF equivalentes serían (con notación Turtle):
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix test: <http://test.com/tbox/people#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
<"http://test.com/abox/people#IDENTIFIER"> rdf:type test:Person ;
rdf:type owl:Thing ;
test:age "24"^^xsd:decimal ;
test:name "Helena"^^xsd:string .
A la hora de crear individuos en la ABox, hay que tener en cuenta que se pueden crear individuos con valores sólo para algunas propiedades, o con propiedades no definidas en la clase OWL o ni siquiera en la TBox. Una de las características de las lógicas descriptivas, la base formal de OWL, es que tienen una semántica denominada de "mundo abierto" (ver el "Cambridge Handbook of DL"). Siguiendo una semántica "open world", si una información no se ha asertado en la ontología, no quiere decir que no exista, sino que todavía no se conoce. Lo mismo sucede para la información extra. Es interesante comparar esta semántica con una semántica de "mundo cerrado", como la que siguen las bases de datos relacionales, donde si un dato no está en la tabla, significa un error, no desconocimiento. Esta situación se agrava cuando contamos con pocos constructores OWL y no hay soporte para un razonador, si no que se trabaja solo con las aserciones explícitas de la ontología.
Una forma de poner restricciones a lo que insertamos en la ABox, sin recurrir a hacer un test de consistencia con un reasoner tras realizar las aserciones, es permitir añadir validaciones a los recursos descritos en la TBox. Estas validaciones se realizan cuando se quiere hacer una aserción en la ABox para ver si la aserción está permitida o no, controlando qué es válido y qué no es válido asertar. Aún así, esta solución no deja de ser una pequeña chapuza, ya que perdemos la información de las validaciones que no está contenida en la ontología, por lo que siempre que sea posible, se debe modelar correctamente la ontología, insertando la información posible y después añadir las validaciones.
Estas validaciones, son predicados y se pueden realizar en el registro de la TBox. y pueden ir asociadas a cualquier acción y recurso. Por ejemplo:
(tbox-register-name! :owl-thing (owl-Thing) :connection)
(tbox-register-name! :test-Person :test "Person" :connection)
(tbox-register-name! :test-age :test "age" :connection)
(tbox-register-name! :test-name :test "name" :connection)
(tbox-register-validation-on! :create
:test-age
(fn [age]
(> (:value age) 18)))
En este caso, comprobamos que al crear un nuevo individuo que da valor a la propiedad test:age, el valor de la edad es mayor que 18. En este caso la validación es para un propiedad, y se comprobaría cuando se realizase una acción sobre cualquier clase con esa propiedad, de esta manera se pueden reutilizar las validaciones entre diferentes clases. Cuando la validación es a nivel de clase, pero no común para las propiedad compartida entre clases, se puede registrar la validación a nivel de clase:
(tbox-register-name! :test-AdultPerson :test "AdultPerson" :connection)
(tbox-register-validation-on! :create
:test-AdultPerson
(fn [properties]
(> (:value (:test-age properties)) 18)))
Así se comprobaría el predicado cuando se crea un individuo de la clase test:AdultPerson, pero no cuando ce crea cualquier otro individuo de una clase relacionada con la propiedad test:age.
Con las validaciones se pueden ofrecer una semántica para casi todo: propiedades obligatorias, formato correcto de valores ... siempre que no sean excusa para perder información que podría haberse asertado algún constructor de OWL, como allValuesFrom, etc.
servido por Antonio
sin comentarios
compártelo
5 Enero 2009
Hoy en día la norma para desarrollar la capa de persistencia de una aplicación web consiste en desarrollar un esquema para algún sistema de base de datos relacional en el que almacenar los datos.
Esto supone básicamente darle golpes a tus objetos hasta convertirlos en conjuntos de enteros, cadenas, etc que después se vuelven a encajar en tablas, indicando relaciones entre esas tablas duplicando valores en columnas especiales de esas tablas. Estamos tan acostumbrados a hacerlo de esta manera que ni nos damos cuenta de la gran distancia que media entre nuestro modelo objetual, con el que trabajamos en nuestro código, y el nivel de abstracción mucho más bajo con el que almacenamos una representación persistente de esos datos.
Sin embargo, esto no tendría porque ser así. Cuando creamos y modelamos nuestro dominio de datos para la aplicación, no hacemos más que crear una ontología, una descripción de los recursos del dominio y sus relaciones, y en los últimos años se ha estandarizado un lenguaje para la descripción de ontologías por parte de la W3C: OWL.

OWL nace del mundo de la lógica descriptiva y permite describir las dos partes que componen una ontología:
- Una TBox: o terminological box, donde describimos una terminología, los tipos de recursos y relaciones, por ejemplo, existen cosas llamadas personas, existe una relación entre una cosa y un número llamada tener edad, etc...
- Una ABox: o assertional box, donde hacemos aserciones usando los términos de la TBox, por ejemplo: Juan es una persona, Juan tiene una edad de 15, etc...
A continuación mostraré una biblioteca para describir una TBox en Clojure lisp.
Nuestra primera tarea es tener un sitio donde almacenar la TBox, para ello debemos tener una conexión con un repositorio. Con el siguiente código creamos un repositorio en memoria y lo registramos con un nombre para usarlo posteriormente:
(register-repository! :test (init-memory-repository!))
A continuación podemos pasar a describir nuestra TBox para la ontología que vamos a describir, la famosa aplicación para escribir blogs, para ello usamos las funciones (describe-owl-*):
(write-graph-in-repository
(describe-tbox
;; algunas propiedades de datos
(describe-owl-datatype-property :superblog "title" (xsd-string))
(describe-owl-datatype-property :superblog "content" (xsd-string))
(describe-owl-datatype-property :superblog "name" (xsd-string))
;; algunas clases
(describe-owl-class :superblog "Blog")
(describe-owl-class :superblog "Post")
(describe-owl-class :superblog "Author")
;; asignamos propiedades a clases
(describe-owl-class-has-property :superblog "Blog" :superblog "title")
(describe-owl-class-has-property :superblog "Post" :superblog "title")
(describe-owl-class-has-property :superblog "Post" :superblog "content")
(describe-owl-class-has-property :superblog "Author" :superblog "name")
;; algunas propiedades de objetos
(describe-owl-object-property :superblog "contains")
(describe-owl-object-property :superblog "writes")
;; relaciones entre clases
(describe-owl-class-has-property :superblog "Blog" :superblog "contains")
(describe-owl-property-range :superblog "contains" :superblog "Post")
(describe-owl-class-has-property :superblog "Author" :superblog "writes")
(describe-owl-property-range :superblog "writes" :superblog "Post"))
;; seleccionamos la conexion con el repositorio anteriormente registrado
(connection! :test))
El código anterior es equivalente a escribir una migración en Ruby on Rails, pero el hecho de usar OWL en vez de directamente una base de datos relacional, nos aporta una serie de beneficios, por ejemplo, la reutilización del esfuerzo empleado en modelar el dominio. En el ejemplo anterior podemos darnos cuenta de que las relaciones como título, ser autor de, etc, ya han sido modelados y estandarizados dentro del Dublin Core Metadata Initiative (http://en.wikipedia.org/wiki/Dublin_Core), ¿por qué no utilizar el soporte para el Dublin Core que incluye la biblioteca? La reutilización se basa en el concepto de kits de vocabularios, que incluyen clases y propiedades y funciones que trabajan sobre estas partículas OWL:
(use 'com.agh.webserver.framework.persistence.rdf.vocabularies.dc)
;; cargamos la descripción del Dublin Core en el repositorio de test
(import-vocabulary-kit :dc (connection! :test))
;; buscamos sinonimos entre nuestro dominio y el DC
(write-graph-in-repository
(describe-tbox
(describe-owl-equivalent-properties :superblog "title" (dc-title))
(describe-owl-equivalent-properties :superblog "writes" (dc-creator)))
(connection! :test))
La descripción de la TBox de la ontología está en este momento almacenada en el repositorio, para poder usar la TBox de forma eficiente debemos iniciar la TBox al empezar a usar nuestra aplicación declarando que recursos de la ontología almacenada en el repositorio queremos utilizar:
;; iniciamos la TBox
(tbox-register-name! :owl-thing (owl-Thing) :test)
(tbox-register-name! :superblog-Post "http://superblog.com/Post" :test)
(tbox-register-name! :superblog-Blog "http://superblog.com/Blog" :test)
(tbox-register-name! :superblog-Author "http://superblog.com/Author" :test)
(tbox-register-name! :superblog-content "http://superblog.com/content" :test)
(tbox-register-name! :superblog-title "http://superblog.com/title" :test)
(tbox-register-name! :superblog-name "http://superblog.com/name" :test)
(tbox-register-name! :superblog-post "http://superblog.com/contains" :test)
(tbox-register-name! :superblog-post "http://superblog.com/writes" :test)
;; iniciamos el kit de Dublin Core con los nombre por defecto que asigna la
:: biblioteca
(tbox-register-vocabulary-dc :test)
;; el codigo de la aplicacion empezaria aqui
Ahora ya podríamos acceder a los recursos mediante su URI o mediante el nombre con el que se ha registrado. Un test que muestra la interacción entre las diferentes partes, además de la inferencia entre propiedades y subclases, podría ser:
(deftest test-class-subclassing
(let [orig-tbox @*tbox*
orig-conn @*connections*
orig-repos @*repositories-registry*
repo (init-memory-repository!)
graph (describe-tbox
(describe-owl-datatype-property "http://test.com/prop_a" (xsd-float))
(describe-owl-datatype-property "http://test.com/prop_b" (xsd-float))
(describe-owl-class "http://test.com/class_a")
(describe-owl-class "http://test.com/class_b")
(describe-owl-class "http://test.com/class_c")
(describe-owl-subclass "http://test.com/class_a" "http://test.com/class_b")
(describe-owl-subclass "http://test.com/class_a" "http://test.com/class_c")
(describe-owl-class-has-property "http://test.com/class_a" "http://test.com/prop_a")
(describe-owl-class-has-property "http://test.com/class_b" "http://test.com/prop_b"))]
(do (tbox-clear!)
(register-repository! :test repo)
(write-graph-in-repository graph (connection! :test))
(tbox-register-name! :owl-thing (owl-Thing) :test)
(tbox-register-name! :class_a "http://test.com/class_a" :test)
(tbox-register-name! :class_b "http://test.com/class_b" :test)
(tbox-register-name! :class_c "http://test.com/class_c" :test)
(tbox-register-name! :prop_a "http://test.com/prop_a" :test)
(tbox-register-name! :prop_b "http://test.com/prop_b" :test)
(let [class-recovered (tbox-find-class-by-uri! "http://test.com/class_a")]
(do
(is (= class-recovered
{:name :class_a
:uri "http://test.com/class_a"
:subclass-of [{:name :class_a
:uri {:prefix "", :value "http://www.w3.org/2002/07/owl#Thing"}
:subclass-of []
:datatype-properties #{}
:object-properties #{}
:repository-name :test}
{:name :class_b
:uri {:prefix "", :value "http://test.com/class_b"}
:subclass-of [{:name :owl-Thing
:uri {:prefix "", :value "http://www.w3.org/2002/07/owl#Thing"}
:subclass-of []
:datatype-properties #{}
:object-properties #{}
:repository-name :test}]
:datatype-properties #{{:name nil
:uri
{:prefix "", :value "http://test.com/prop_b"}
:range {:prefix "", :value "http://www.w3.org/2001/XMLSchema#float"}
:equivalent-properties #{}
:repository-name :test}}
:object-properties #{}
:repository-name :test}
{:name :class_c
:uri {:prefix "", :value "http://test.com/class_c"}
:subclass-of [{:name :owl-Thing
:uri {:prefix "", :value "http://www.w3.org/2002/07/owl#Thing"}
:subclass-of []
:datatype-properties #{}
:object-properties #{}
:repository-name :test}]
:datatype-properties #{}
:object-properties #{}
:repository-name :test}]
:datatype-properties #{{:name :prop_b
:uri {:prefix "", :value "http://test.com/prop_b"}
:range {:prefix "", :value "http://www.w3.org/2001/XMLSchema#float"}
:equivalent-properties #{}
:repository-name :test}
{:name :prop_a
:uri
{:prefix "", :value "http://test.com/prop_a"}
:range {:prefix "", :value "http://www.w3.org/2001/XMLSchema#float"}
:equivalent-properties #{}
:repository-name :test}}
:object-properties #{}
:repository-name :test}
(tbox-restore! orig-tbox)
(repositories-register-restore! orig-repos)
(connections-restore! orig-conn))))))
Al igual que OWL se situa sobre una pila de protocolos de la W3C para la web semántica, en concreto, una ontología OWL se codifica como un grafo RDF que a su vez se puede describir como un documento XML, la biblioteca OWL usa la bibliteca RDF que comenté anteriormente para describir los recursos en términos de constructores de un grafo RDF y recupera su información transformando ese grafo en consultas SPARQL.
En siguientes posts, veremos como usar esta TBox para hacer aserciones sobre objetos concretos dentro de la ABox.
Aunque todavía se encuentra muy lejos de ser usable para nada, si queréis hacer pruebas y juguetear con él, el código se encuentra disponible en mi repositorio de Github.
servido por Antonio
sin comentarios
compártelo
1 Enero 2009
El código y los tests los podéis encontrar en mi repositorio de Github.
Usando Sesame como repositorio semántico. Describimos un grafo RDF:
(def *graph*
(build-graph
[(build-uri-node "http://test.com/whatever"
[(build-relation :rdf "relation-1" (build-literal-node "testa1"))
(build-relation :rdf "relation-2" (build-literal-node "testa2"))])
(build-uri-node :rdf "test-subjectb"
[(build-relation :rdf "relation-2" (build-blank-node "a" []))])])
El grafo que acabamos de describir es quivalente a los siguientes tripletes RDF (con notación turtle):
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
<http://test.com/whatever> rdf:relation-1 "testa1" .
<http://test.com/whatever> rdf:relation-2 "testa2" .
rdf:test-subjectb rdf:relation-2 _:a .
El grafo que hemos guardado en *graph* podemos manipularlo en memoria, pero para almacenarlo, debemos obtener una conexión con un repositorio. Supongamos que tenemos una base de datos llamada "clojure_sesame", podemos usarla para almacenar el grafo en esa base de datos:
(def *connection*
(. (init-repository "com.mysql.jdbc.Driver"
"jdbc:mysql://localhost:3306/clojure_sesame"
"root"
"root")
getConnection)
(write-graph-in-repository *graph* *connection*)

Una funcionalidad interesante es consultar un grafo RDF de una forma similar al uso que se hace de SQL para consultar un base de datos relacional.
Para dar solución a esta necesidad se ha estandarizado SPARQL (SPARQL Protocol and RDF Query Language) por parte de la W3C (http://www.w3.org/TR/rdf-sparql-query/).
La biblioteca ofrece esta funcionalidad mediante la construcción de plantillas: grafos RDF, donde se pueden insertar constructores para nodos variables y para relaciones variables:
(def *template*
(build-graph-template
[{:template (build-graph
[(build-variable-node :x
[(build-variable-relation :y
(build-variable-node :z []))])])
:filters []}])]
La función (build-graph-template) acepta mapas con dos claves, :template que debe contener como valor un grafo con partes variables, y :filters, una colección de filtros que se pueden aplicar a los nodos variables. Por ejemplo:
(build-filter :x "?x < 25")
(build-filter :y "isURI(?y)")
También se pueden describir partes opcionales de la plantilla con la función (build-optional-graph).
Una vez que tenemos la plantilla debemos seleccionar que variables queremos que la consulta nos devuelva. En el ejemplo anterior, tenemos una plantilla con tres variables :x :y :z, si sólo nos interesan las variables :x e :y, construimos la consulta de la siguiente manera:
(def *result*
(query-template-in-repository
:x :y
*template*
*connection*)
La invocación anterior es equivalente a realizar la siguiente consulta SPARQL:
SELECT ?x ?y WHERE { ?x ?y ?z .}
Los resultados se devuelven como colecciones de mapas, donde cada mapa contiene una solución para :x e :y:
;; El resultado de la consulta
[{:y {:prefix "", :value "http://www.w3.org/1999/02/22-rdf-syntax-ns#relation-2"}
:x {:prefix "", :value "http://test.com/test-subjecta/whatever"}}
{:y {:prefix "", :value "http://www.w3.org/1999/02/22-rdf-syntax-ns#relation-1"}
:x {:prefix "", :value "http://test.com/test-subjecta/whatever"}}
{:y {:prefix "", :value "http://www.w3.org/1999/02/22-rdf-syntax-ns#relation-2"}
:x {:prefix "", :value "http://www.w3.org/1999/02/22-rdf-syntax-ns#test-subjectb"}}]
Las plantillas no solo describen patrones para consultas SPARQL, sino que se pueden sustituir las partes variables de la plantilla, si queremos transformarla en un grafo de nuevo. Usando el primer resultado de la consulta, podemos hacer:
(template-to-graph *template* (first *result*))
Y obtendríamos el grafo equivalente resolviendo :x e :y (en este caso todavía quedaría la parte variable referente a :z).
También podemos usar la capacidad de sustitución de las plantillas para dar valores parciales a variables al hacer una consulta:
(query-template-in-repository
:x :y {:z (build-literal-node "testa1")}
*template*
*connection*)
De esta manera obtendríamos una consulta equivalente a:
SELECT ?x ?y
WHERE {
?x ?y "testa1" .
}
Esta biblioteca, ofrece un acceso a RDF de bastante bajo nivel, manipulando tripletes de forma individual, pero puede servir de base para construir una capa de persistencia semántica para aplicaciones web, quizá construida sobre OWL, el lenguaje de descripción de ontologías de la W3C.
servido por Antonio
sin comentarios
compártelo
21 Octubre 2008
Un post rápido, comparando implementaciones de un algoritmo en su aproximación funcional e imperativa.
Cuando empezamos a programar, normalmente lo hacemos en un lenguaje imperativo, y el primer programa que tecleamos es nuestro querido "hola mundo". Bien, en el mundo funcional existe un equivalente al hola mundo que suele ser escribir una función que genere la serie de fibonacci, pero creo que será más interesante algo un poquito más complicado, usemos el quicksort:
1-pseudocódigo
function quicksort(array)
var list less, greater
if length(array) ≤ 1
return array
select and remove a pivot value pivot from array
for each x in array
if x ≤ pivot then append x to less
else append x to greater
return concatenate(quicksort(less), pivot, quicksort(greater))
2.- C
void Quicksort(int[] v, int prim, int ult) {
if (prim < ult) {
int p = Pivot(v, prim, ult, ult);
Quicksort(v, prim, p - 1);
Quicksort(v, p + 1, ult);
}
}
int Pivot(int[] v, int prim, int ult, int piv) {
int p = v[piv];
int j = prim;
swap(v, piv, ult);
for (int i = prim; i < ult; i++) {
if (v[i] <= p) {
swap(v, i, j);
j++;
}
}
swap(v, j, ult);
return j;
}
void swap(int[] v, int a, int b) {
if (a != b) {
int tmp = v[a];
v[a] = v[b];
v[b] = tmp;
}
}
3.-Java
public class QuickSort {
public static void swap (int A[], int x, int y) {
int temp = A[x];
A[x] = A[y];
A[y] = temp;
}
public static int partition(int A[], int f, int l) {
int pivot = A[f];
while (f < l) {
while (A[f] < pivot) f++;
while (A[l] > pivot) l--;
swap (A, f, l);
}
return f;
}
public static void Quicksort(int A[], int f, int l) {
if (f >= l) return;
int pivot_index = partition(A, f, l);
Quicksort(A, f, pivot_index);
Quicksort(A, pivot_index+1, l);
}
}
4.-Clojure Lisp
(defn qsort
([] [])
([x & xs] (concat (apply qsort (filter #(< % x) xs))
(cons x (apply qsort (filter #(>= % x) xs))))))
5.-Haskell
qsort [] = []
qsort (x:xs) = qsort smaller ++ [x] ++ qsort bigger
where smaller = filter (<x) xs
bigger = filter (>=x) xs
Los ejemplos están sacados de wikipedia, menos el de Clojure Lisp. Lo dicho, busque, compare, etc.
servido por Antonio
sin comentarios
compártelo
4 Septiembre 2008
Escribir una extensión para firefox que utilice un comonente XPCOM implementado en C++ es mucho más sencillo que lo que muestra el caótico estado de la documentación en Mozilla Developers. Por eso, he pensado que sería interesante hacer un minitutorial que recoja en un solo sitio lo que me ha tocado buscar en esta documentación, foros, etc...

Lo primero que hay que dejar claro, el entorno para el que se va a construir el plugin:
- Firefox 3.0 (Gecko SDK 1.9)
- Mac OS X 10.5
El componente XPCOM que vamos a construir se distribuye como una biblioteca compartida con la extensión, por lo que se debe recompilar contra la versión del SDK de Gecko para la plataforma objetivo, de tal forma que se obtendrá un fichero .dylib en Mac OS X, un fichero .so en Linux o un fichero .dll en Windows. Los makefile y el flags que voy a utilizar son los necesarios para construir la biblioteca en Mac.
¿Qué pinta tiene el plugin que vamos a construir? pues será un plugin de test, para demostrar la arquitectura de los plugin de Mozilla, pero sin mucha funcionalidad. Lo único que va a hacer es sumar dos números.
Las partes de una extensión firefox son las siguientes:
- Un componente XPCOM: un conjunto de funciones que ofrecen una funcionalidad determinada con el objetivo de ser reutilizados fácilmente. Esta interfaz se expresa en un lenguaje de definición de interfaces (IDL) independiente del lenguaje de implementación. Luego, esta lógica la implementaremos en C++ y podrá ser reutilizada por código escrito en cualquier otro lenguaje, en nuestro caso javascript.
- Una interfaz gráfica de usuario descrita en el lenguaje XUL: XUL es un lenguaje basado en XML que permite describir una interfaz gráfica de usuario mediante widgets: botones, ventanas, etc. Luego estos ficheros XML son convertidos en widgets en tiempo de ejecución por el motor de renderizado Gecko, del mismo modo en que se renderiza el HTML de una página web. El código XUL es accesible a través de DOM como cualquier otro documento XML. Por ejemplo, toda la interfaz de firefox está descrita en un solo fichero XUL. Además utilizaremos otra funcionalidad intersante los "overlays" porciones de interfaz descritas en XUL que pueden modificar otra interfaz XUL, por ejemplo, usaremos overlays para insertar un botón dentro de la barra de estado del navegador firefox.
- Código de pegamento escrito en javascript: por ejemplo para asignar un callback que se ejecutará cuando el usuario haga click en el botón XUL anteriormente comentado y mostrar un nuevo diálogo, o acceder al componente XPCOM que vamos a escribir en C++. Básicamente usaremos javascript para pegar la otras piezas que construyamos.
- Código de empaquetado, registro y distribución de la extensión: todo lo necesario para empaquetar la extensión en un formato de fichero especial llamado XPI (lease zippy) que se puede colgar en un servidor web y hacer que se instale automáticamente mediante el instalador de plugins del navegador firefox con una tecnología denominada XPInstall.
Antes de empezar una precisión, la extensión que vamos a construir es para firefox, pero el componente XPCOM se puede utilizar desde cualquier plataforma mozilla (thunderbird, sunbird, etc). Hay que pensar que firefox es una aplicación que se ha construido sobre una plataforma mucho más amplia y general, con la que se han construido diferentes productos.
Empezamos. Primero necesitamos las herramientas de desarrollo, en Mac, hay que tener instalado las herramientas para desarrolladores que vienen con XCode: compilador, enlazador, autotools, etc, si no lo tienes instalado, buscalo en el segundo disco de instalación de Mac OS X o bájalo del Apple Developer Connection.
Lo siguiente es conseguir el SDK de Gecko, aquí empiezan los problemas, porque el nombre de la plataforma y su organización interna ha cambiado numerosas veces. Incluso hay gente que descargan directamente el código fuente de Firefox (>4*10^6 líneas de código) para obtener una versión de la plataforma de desarrollo. En cualquier caso, la versión actual del SDK se puede descargar desde esta página: http://developer.mozilla.org/index.php?title=En/Gecko_SDK&highlight=Gecko+SDK La versión para firefox 3 es la 1.9. Deberemos descargar el SDK y descomprimirlo en algún directorio de nuestro sistema de ficheros. (El fichero descomprimido se llama xulrunner-sdk).
Ahora podemos empezar a construir nuestro componente. El primer paso es definir la interfaz a través de la que se va a poder acceder a nuestra lógica de negocio. Para ello necesitamos dos cosas: un identificador único para nuestra interfaz y una definición de la misma en IDL (¿alguien dijo CORBA?).
El identificador único, se puede conseguir en UNIX usando el comando uuidgen, que generara por nosotros el identificador, por ejemplo:
$ uuidgen
7F5D8FC8-CF63-474D-8C70-31B6CAEC3201
A continuación escribiremos la definición de la interfaz, en nuestro caso tendrá una sola función que sumará dos números y además indicaremos en el mismo fichero el identificador que acabamos de generar:
fichero IMyComponent.idl
#include "nsISupports.idl"
[scriptable, uuid(7F5D8FC8-CF63-474D-8C70-31B6CAEC3201)]
interface IMyComponent : nsISupports
{
long Add(in long a, in long b);
};
El lenguaje en el que describimos la interfaz es parecido al IDL del OMG, la referencia completa se puede obtener en: http://www.mozilla.org/scriptable/xpidl/idl-authors-guide/index.html Recuerda que debes sustituir el UUID por el que has generad con uuidgen.
Con la interfaz descrita, el siguiente paso es compilarla para genera un fichero de tipo de datos binarios, que permitirá la interoperabilidad con otros lenguajes y una plantilla con el fichero de cabecera C para implementar la interfaz en este lenguaje. Para realizar esta tarea usamos la herramienta xpidl que viene incluida en la distribución del Gecko SDK por ejemplo:
$PATH_TO/xulrunner-sdk/bin/xpidl -I PATH_TO/xulrunner-sdk/sdk/idl/ -m header IMyComponent.idl
$PATH_TO/xulrunner-sdk/bin/xpidl -I PATH_TO/xulrunner-sdk/sdk/idl/ -m typelib IMyComponent.idl
Ten en cuenta que hay que ejecutar ambos comandos, en el primero se genera la plantilla y en la segunda el fichero binario.
Si todo ha ido bien, podremos empezar a implementar la clase C++ que implementará la interfaz que acabamos de generar, para ello podemos usar el fichero con la plantilla gnerada: IMyComponent.h. Copia el código desde el comentario
/* Header file */
class _MYCLASS_ : public IMyComponent
{
public:
NS_DECL_ISUPPORTS
NS_DECL_IMYCOMPONENT
_MYCLASS_();
private:
~_MYCLASS_();
protected:
/* additional members */
};
en un fichero llamado MyComponent.h y modficalo sustituyendo los valores _MYCLASS_ por MyComponente. Además, se debe añadir información con una descripción de la interfaz, un identificador de contrato y un nuevo uuid para el componente (el anterior era para la interfaz), debes obtener algo similar a lo siguiente
fichero MyComponent.h
#ifndef _MY_COMPONENT_H_
#define _MY_COMPONENT_H_
#include "IMyComponent.h"
#define MY_COMPONENT_CONTRACTID "@goomer.com/MyComponent"
#define MY_COMPONENT_CLASSNAME "A Simple XPCOM Sample"
#define MY_COMPONENT_CID { 0x786b1ad1, 0x4a63, 0x44ac, \
{ 0x96, 0x84, 0xca, 0x8b, 0x99, 0x43, 0x6f, 0x93 } }
class MyComponent : public IMyComponent
{
public:
NS_DECL_ISUPPORTS
NS_DECL_IMYCOMPONENT
MyComponent();
~MyComponent();
protected:
/* additional members */
};
#endif
Intentaré explicar un poco que significa el código el fichero. Un componente XPCOM puede implementar una o varias interfaces, pero al menos debe implementar la interfaz nsISupports. El fichero de cabecera de la clase que vaya a implementar esas interfaces debe declarar con funciones de la api de XPCOM que interfaces va a implementar, la forma más rápida de hacerlo es utilizar las macros de la biblioteca XPGLUE, de ahí salen las líneas NS_DECL_ISUPPORTS y NS_DECL_IMYCOMPONENT, que indican que esta clase implementará la interfaz nsISupports y nuestra interfaz IMyComponent. Por otro lado, para poder obtener una referencia a esta clase, se necesita un identificador por el que preguntar, este identificador puede ser un uuid o una cadena con el formato como el que aparece en el código anterior en las líneas:
#define MY_COMPONENT_CONTRACTID "@goomer.com/MyComponent"
#define MY_COMPONENT_CLASSNAME "A Simple XPCOM Sample"
#define MY_COMPONENT_CID { 0x786b1ad1, 0x4a63, 0x44ac, \
{ 0x96, 0x84, 0xca, 0x8b, 0x99, 0x43, 0x6f, 0x93 } }
Como observarás, el formato del CID es diferente al de los otros UUID, puedes obtener este formato pasando el parámetro -hdr a uuidgen o puedes visitar este enlace que te generará uno automáticamente:
http://mozilla.pettay.fi/cgi-bin/mozuuid.pl
El siguiente paso es implementar la interfaz, para ello volvemos a copiar la sección de la plantilla que con el código de implementación de la clase:
/* Implementation file */
NS_IMPL_ISUPPORTS1(_MYCLASS_, IMyComponent)
_MYCLASS_::_MYCLASS_()
{
/* member initializers and constructor code */
}
_MYCLASS_::~_MYCLASS_()
{
/* destructor code */
}
/* long Add (in long a, in long b); */
NS_IMETHODIMP _MYCLASS_::Add(PRInt32 a, PRInt32 b, PRInt32 *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
Lo copiamos en un fichero MyComponent.cpp e implementamos el código de la funcion Add:
fichero MyComponent.cpp
#include "MyComponent.h"
#include "xpcom-config.h"
#include "nsIClassInfoImpl.h"
NS_IMPL_ISUPPORTS1(MyComponent, IMyComponent)
MyComponent::MyComponent()
{
/* member initializers and constructor code */
}
MyComponent::~MyComponent()
{
/* destructor code */
}
/* long Add (in long a, in long b); */
NS_IMETHODIMP MyComponent::Add(PRInt32 a, PRInt32 b, PRInt32 *_retval)
{
*_retval = a + b;
return NS_OK;
}
En este código se usa la macro NS_IMPL_ISUPPORTS1 que indica como se implementa la interfaz IMyComponent y además ofrece una implementación por defecto de la interfaz nsISupport.
El último fichero que debemos crear es el fichero con el código que declara el módulo en el que va insertado el componente, y que debe ofrecer un método factory para construir el objeto que implementa la interfaz del componente, su código es muy sencillo:
fichero MyComponentModule.cpp
#include "MyComponent.h"
#include "nsIGenericFactory.h"
#include "xpcom-config.h"
#include "nsIClassInfoImpl.h"
NS_GENERIC_FACTORY_CONSTRUCTOR(MyComponent);
static nsModuleComponentInfo components[] =
{
{
MY_COMPONENT_CLASSNAME,
MY_COMPONENT_CID,
MY_COMPONENT_CONTRACTID,
MyComponentConstructor,
}
};
NS_IMPL_NSGETMODULE(MyComponentModule, components);
Cómo se puede observar, seguimos usando las macros de XPCOMGLUE para facilitarnos la labor de escribir aburrido código de infraestructura.
Ya está preparados para compilar el componente, edita un fichero llamado Makefile con el siguiente contenido:
fichero Makefile
CXX = c++
CPPFLAGS = -dynamiclib
GECKO_SDK_PATH = PATH_TO/xulrunner-sdk/sdk
GECKO_INCLUDES = -I $(GECKO_SDK_PATH)/include
GECKO_LD_FLAGS = -L$(GECKO_SDK_PATH)/lib -L$(GECKO_SDK_PATH)/bin -Wl,-executable_path,$(GECKO_SDK_PATH)/../bin -lxpcomglue_s -lxpcom -lnspr4
build:
$(CXX) -Wall -Os -o MyComponentModule.dylib $(GECKO_CONFIG_INCLUDE) $(GECKO_DEFINES) $(GECKO_INCLUDES) $(GECKO_LD_FLAGS) $(CPPFLAGS) $(CXXFLAGS) MyComponent.cpp MyComponentModule.cpp
chmod +x MyComponentModule.dylib
clean:
rm MyComponentModule.dylib
Como se puede ver un makefile muy simple que incluye las opciones para generar la librería dinámica y enlaza con el framework de GECKO.
Lanzamos un
$make
Y si todo va bien, deberíamos obtener el fichero MyComponentModule.dylib, que contendra la lógica de nuestra extensión.
Antes de empezar a diseñar la interfaz gráfica de usuario de la misma, vamos a construir la estructura de directorios para empaquetar el componente. En un directorio vació crea la estructura siguiente:
|------chrome/
| |
| |------content/
|
|------components/
Y copia los ficheros MyComponentModule.dylib y IMyComponent.xpt generados anteriormente al directorio components
El resto de ficheros de la extensión los crearemos dentro del diretorio chrome/content/
Construiremos en primer lugar un fichero javascript con el código necesario para acceder al componente que acabamos de crear, crea el fichero MyComponent.js en el directorio chrome/content/
fichero MyComponent.js
function sumThem() {
tf1 = document.getElementById("dialog.input1").value;
tf2 = document.getElementById("dialog.input2").value;
var mycomponent = Components.classes["@goomer.com/MyComponent"].getService();
mycomponent = mycomponent.QueryInterface(Components.interfaces.IMyComponent);
tf3 = mycomponent.Add(tf1,tf2);
document.getElementById("dialog.output").value = tf3;
}
function loadMyComponent() {
openDialog("chrome://mycomponent/content/MyComponent.xul");
}
Como se puede observar la función sum utiliza el identificador CID que hemos asignado al componente para obtener una referencia al mismo, busca la interfaz que nos interesa de entre las que este componente implementa y, por fin invoca al método Add de dicha interfaz.
Los valores para ser sumados los obtiene la función del valor de dos elementos a los que accede a través de los métodos DOM getElementById. Estos elementos son partes de la interfaz de usuario que vamos a especificar a continuación en XUL. Del mismo modo la función loadMyComponent abre un diálogo a descrito en una fichero XUL que crearemos a continuación. Por último, se puede observar como al cargar este último fichero, se sigue la ruta de la estructura de directorios que acabamos de crear.
Pasemos pues a describir la interfaz de usuario, para ello añadiremos dos ficheros más a nuestro directorio content. El primero MyComponent.xul describe un cuadro de diálogo con tres campos de texto (2 para los sumando y 1 para el resultado) además de un botón que ejecutará al recibir un clic la función javascript sumThem que acabamos de implementar:
fichero MyComponent.xul
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<dialog id="MyComponent dlg"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="MyComponent Test"
style="width: 30em;"
persist="screenX screenY"
screenX="24" screenY="24">
<hbox>
<separator orient="vertical" class="thin"/>
<vbox flex="1">
<separator class="thin"/>
<hbox align="center">
<textbox id="dialog.input1" flex="1" />
<textbox id="dialog.input2" flex="1" />
<button label="Sum"
onclick="sumThem();"/>
</hbox>
<hbox align="center">
<textbox id="dialog.output" flex="1" />
</hbox>
</vbox>
</hbox>
</dialog>
Como se puede observar, en este componente se llama a la función sumThem definida en el fichero javascript anterior mediante un callback en el botón de la interfaz XUL.
El otro fichero de interfaz es el overlay, que describe un botón con una etiqueta que se "mezclará" con el fichero XUL que describe la interfaz de firefox, de modo que la interfaz del navegador se modificará para mostrar el botón descrito en el overlay en la barra de estado del naveagdor.
fichero overlay.xul
<?xml version="1.0"?>
<overlay id="sample"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<statusbar id="status-bar">
<statusbarpanel id="my-panel" label="Hello, World" onclick="loadMyComponent()"/>
</statusbar>
</overlay>
En ambos ficheros XUL referencial el fichero javascript a través de la ruta del componente que estamos construyendo.
Ahora el componente está terminado, falta escribir los ficheros necesarios para distribuir la extensión, estos ficheros contienen metainformación sobre el plugin que firefox utilizará para registrar e iniciar el plugin.
El primer fichero se llama chrome.manifest y debe crearse al mismo nivel que el directorio chrome. En este fichero se describen aquellos contenidos que deben registrarse en el chrome de firefox. En nuestro caso, el único componente es el overlay, para el que deberemos indicar con que fichero debe ser mezclado. Especificaremos que este fichero de destino es el fichero XUL que describe la interfaz del navegador. Adicionalmente, también indicaremos que directorio dentro de la extensión es la que contiene los contenidos.
fichero chrome.manifest
content maycomponent chrome/content/
overlay chrome://browser/content/browser.xul chrome://mycomponent/chrome/sample.xul
El segundo fichero de configuración es un documento RDF llamado install.rdf y que contiene metadatos sobre el autor de la extensión, versión y versión de firefox con la que es compatible. El UUID que aparece en el fichero es el que identifica a firefox, ese valor debe ser el mismo para todas las extensiones.
fichero install.rdf
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>mycomponent@goomer.com</em:id>
<em:version>1.0</em:version>
<em:type>2</em:type>
<!-- Target Application this extension can install into,
with minimum and maximum supported versions. -->
<em:targetApplication>
<Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>3</em:minVersion>
<em:maxVersion>3.0.*</em:maxVersion>
</Description>
</em:targetApplication>
<!-- Front End MetaData -->
<em:name>My Component</em:name>
<em:description>A test extension</em:description>
<em:creator>Your Name Here</em:creator>
<em:homepageURL>http://www.example.com/</em:homepageURL>
</Description>
</RDF>
Nuestro componente está listo para ser empaquetado y distribuido, en este momento, nuestro arbol de directorios debe tener el siguiente aspecto:
|------install.rdf
|------chrome.manifest
|------chrome/
| |
| |------content/
| |------------MyComponent.js
| |------------MyComponent.xul
| |------------overlay.xul
|
|------components/
|---------------MyComponentModule.dylib
|---------------IMyComponent.xpt
Ahora debemos crear el fichero XPI que será el que se instale en el navegador, para ello comprimimos el árbol anterior en un fichero .ZIP y le cambiamos la extension por XPI. Nuestra extensión está lista para ser instalada. Para que podamos probarla, podemos escribir un fichero html con un pequeño script javascript que inicia la instalación de la extensión mediante la tecnología XPInstall.
<dialog id="MyComponent dlg"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="MyComponent Test"
style="width: 30em;"
persist="screenX screenY"
screenX="24" screenY="24">
<a href="mycomponent.xpi"
hash= "md5:37a50076a58a4be50bb88ca12102fc62"
onclick="return install(event);">Instalame!!!</a>
Para el valor del atributo hash, puedes utilzar la utilidad de linea de comandos md5 que genera le valor de hash para un fichero, si no indicas un valor de hash correcto, el proceso de instalació fallará con una advertencia de seguridad.
Si ahora abres con firefox el fichero test.html y haces clic en el enlace, se inicará el proceso de instalación de la extensión y tras reiniciar el navegador, abajo a la derecha debería aparecer el botón XUL descrito en el overlay.

Si se hace clic en el botón, el cuadro de texto aparecerá, y al introducir los valores deseados y hacer clic en el botón de sumar, se utilizará el componente XPCOM para realizar la suma.

Para comprobar que el componente está correctamente instalado puedes utilizar una extensión llamada XPCOMViewer, que lista todos los componentes instalados con sus interfaces y métodos http://xpcomviewer.mozdev.org/.

¡Felicidades has terminado tu primera extensión para Firefox! El siguiente paso es la documentación de Mozilla para empezar hacer componentes y extensiones más útiles, que utilicen la funcionalidad ofrecida por la gran colección de componentes distribuidos con firefox.
servido por Antonio
1 comentario
compártelo