jueves, 12 de septiembre de 2013

Terminamos el proyecto

Esto se va acabando


Sólo nos queda un método. El dichoso “findEndOfTag” que nos indica donde acaba un elemento. Para ello tenemos que pasarle como parámetro la posición en que comienza dicho elemento:

def findEndOfTag(start)
  if @nodes == nil
   raise 'Not initialized'
  end

  lasttag = @nodes.length - 1
  ending = lasttag

  if @nodes[start].class.name != "HTMLTag"
   ending = start
  elsif isClosed?(start)
   ending = start
  else
   tname = @nodes[start].name
   tagsfound = 0
   (start..lasttag).each do |x|
    if @nodes[x].class.name == "HTMLTag"

       and @nodes[x].name == tname
       and not isClosed?(x)
         tagsfound += 1
    elsif @nodes[x].class.name == "HTMLClose" and @nodes[x].name == tname
         tagsfound -= 1
    end

    if tagsfound == 0
     ending = x
     break
    end
   end
  end


  return ending

end

Para empezar, asumimos que, si no demostramos otra cosa, el objeto acabará al final del documento:

lasttag = @nodes.length - 1
ending = lasttag

Ahora bien, los elementos que no son etiquetas HTML sólo tienen un nodo. O sea: acaban donde empiezan. Y lo mismo ocurre con las etiquetas HTML que, como mencionamos anteriormente y por una u otra razón, se autocierran:

if @nodes[start].class.name != "HTMLTag"
  ending = start
elsif isClosed?(start)
  ending = start

En otro caso, miraremos uno a uno los nodos a partir de la apertura de la etiqueta. Hay que tener en cuenta que podemos encontrarnos con etiquetas del mismo nombre de forma anidada, como en:

<div>
abcde <div id=abcde> HOLA </div>
</div>

… así que anotamos el nombre de la etiqueta cuyo final andamos buscando y llevamos la cuenta de cuántas etiquetas de apertura no autocerradas y cuántas etiquetas de cierre vamos encontrando con el mismo nombre.

tname = @nodes[start].name
tagsfound = 0
(start..lasttag).each do |x|
  if @nodes[x].class.name == "HTMLTag"
    and @nodes[x].name == tname
    and not isClosed?(x)
      tagsfound += 1
  elsif @nodes[x].class.name == "HTMLClose" and @nodes[x].name == tname
      tagsfound -= 1
  end



Cuando el número de aperturas y de cierres sea el mismo hemos encontrado el final y podemos salir del bucle. Precisamente para eso es para lo que sirve la instrucción “break”:

if tagsfound == 0
  ending = x
  break

Y tras cerrar con sus correspondientes “end” todo lo que hemos ido abriendo, retornamos la posición en que acaba la etiqueta:

 
return ending

 

Chin pon


¡Eh! ¡Espera!

 
Aún no hemos acabado. Que no se nos olvide poner un último “end”. El que cierra la definición de la clase:

 
end # class HtmlDoc


Ahora sí. Ya podemos sacar conclusiones.

La clase HTMLDoc supone un paso más de abstracción en la representación de documentos HTML. Un paso que, gracias al paradigma de la programación orientada a objetos, se aprovecha del trabajo que ya habíamos hecho con clases como HTMLParser.


Ahora podremos hacer programas más resultones. Y nos costará mucho menos trabajo.

Aunque, claro, está muy bien eso de poder gestionar el código HTML, pero... ¿de donde lo sacamos? Creo que estaréis de acuerdo en que es de los servidores web de donde uno suele conseguir código HTML más frecuentemente. Así que necesitaremos una clase que interactúe con ellos.

Pero eso es otra historia








miércoles, 11 de septiembre de 2013

La reproducción de los objetos

La reproducción de los objetos


En el colegio me enseñaron que “los seres vivos nacen, crecen, se reproducen y mueren”. Por lo visto los HTMLDoc son seres vivos. Nacen cuando los crea una llamada a “new”. Crecen cuando les añadimos código HTML con cosas como “insertAfter” o “insertBefore”. Mueren cuando se les hace una llamada a “clear” cuando eliminamos nodos.

Y se reproducen, porque un objeto puede generar otros de su misma clase mediante “getElement”. Que es el único método que me queda por pagaros. Así que ahí va:

 
def getElement(start)
  if not (0..(@nodes.length - 1)).include?(start)
   raise 'Element out of range'
  end

  ending = findEndOfTag(start)

  new_element = HtmlDoc.new
  new_element.type = :node
  new_element.nodes = @nodes
  new_element.starts_at = start
  new_element.ends_at = ending
  new_element.document = typeOf == :document ? self : @document

  new_element.document.nodes_created.push(new_element)
  return new_element
end

 
Le pasamos un número con la posición en que comienza el código HTML del objeto que queremos retornar y “getElement” hace el resto. Primero comprobamos que la posición que nos dan es correcta. Si todo va bien, seguiremos con

ending = findEndOfTag(start)

… que asigna a “ending” la posición en que acaba el código HTML del objeto. Y para ello llamamos al método “findEndOfTag”... que aún no hemos definido. ¡Jolín! ¡Ahora que estábamos casi acabando y nos aparece otro método que dejaremos para más adelante!

Tranquis, que tampoco es para tanto, y sigamos. Creamos un nuevo objeto de la clase HTMLDoc para representar el elemento:

new_element = HtmlDoc.new

… e inicializamos sus valores, par decir que es un nodo (no un documento completo), que comparte los nodos de su padre (que en algo se tenía que parecer a él), indicar donde comienza y acaba y señalar al documento al que pertenece:

new_element.type = :node
new_element.nodes = @nodes
new_element.starts_at = start
new_element.ends_at = ending
new_element.document = typeOf == :document ? self : @document

Fijaos en que las invocaciones a “self” o “typeOf” se refieren al objeto padre, sobre el que estamos invocando el método, no al hijo que estamos creando. Y recordad que podemos modificar un objeto desde otro porque definimos los accesores como “protegidos”.

Y otra cosa: en la asignación "new_element.nodes = @nodes" hay que tener en cuenta que cuando asignamos a una variable un objeto en Ruby NO creamos una nueva copia del objeto. De modo que el atributo @nodes del nodo que acabamos de crear y el de su padre harán referencia a un mismo objeto.

Como prueba valga el siguiente ejemplo:

$ irb
irb(main):001:0> a = [1,2,3]
=> [1, 2, 3]
irb(main):002:0> b = a
=> [1, 2, 3]
irb(main):003:0> a[2] = 1     # Modificamos el array original
=> 1
irb(main):004:0> b
=> [1, 2, 1]
irb(main):005:0> # ... y b también ha sido modificado

Para crear copias de un objeto tenemos el método clone

irb(main):006:0* c = a.clone    # c es ahora una copia de a
=> [1, 2, 1]
irb(main):007:0> a[1]=100    # Si modificamos a...
=> 100
irb(main):008:0> c 
=> [1, 2, 1]
irb(main):009:0>  # c sigue igual

Sigamos. Al documento original hay que decirle que hemos creado un nuevo elemento a partir de él, para que vaya llevando la cuenta:

new_element.document.nodes_created.push(new_element)

Y... listo. Sólo queda retornar el nuevo elemento creado:

 
return new_element