Para ti y sólo para ti
Y, para que nadie diga que me escabullo
de mis promesas con la facilidad de un pez, comencemos ya con los
métodos privados. Lo primero es una línea que marque claramente el
territorio. Ponemos:
... y a partir de aquí, lo que
pongamos será privado. Sólo accesible por el propio objeto.
Comencemos con el método “tagRange”,
que nos proporciona el rango de los nodos que representan el objeto:
def
tagRange if typeOf == :deleted raise "Deleted"
end return (@starts_at .. @ends_at) end
|
Recuerda que con “..” creábamos
rangos. Así:
… se corresponde con el rango de
números enteros formado por 5, 6, 7, 8 y 9.
Éste ha sido muy sencillete. Veamos
ahora cómo nos las ingeniamos con “nodesHTLM”, que recibe como
parámetro un rango y nos genera una cadena de texto con el contenido
HTML de los nodos comprendidos en ese rango:
def
nodesHTML(range) return range.collect{|x|
@nodes[x].to_s}.join end |
Tampoco es que haya sido muy
complicado. Resolvamos otro problema. Sabemos que una etiqueta HTML
puede contener su propio “cierre” y no tener contenido interno,
como en:
La barra “/” que precede al “>”
indica que no tenemos que andar buscando una etiqueta de cierre del
tipo “</div>”. Pero es que hay algunas etiquetas, como
“<img ...>” o “<br>” que no necesitan etiqueta de
cierre. Se cierran ellas solas, incluso si no les ponemos la barra al
final. Para darles un tratamiento “personalizado”, vamos a crear
un método que nos detecte si el objeto actual representa una de
estas etiquetas o si incluye de forma explícita la barra al final
para autocerrarse:
def
isClosed?(start=@starts_at) if @nodes[start].class.name !=
"HTMLTag" raise 'Not a tag' end
return (@nodes[start].autoclosed or
['meta','link','img','br','hr','wbr','input','source','param','frame',
'keygen','area','base','batsefon','col','embed','bgsound'].include?
(@nodes[start].name.downcase))
end
|
El método crea un array con los
nombres de las etiquetas que se cierran ellas solitas incluso sin
barra y comprueba si el nombre de la etiqueta representada por el
objeto está en dicho array. Sencillo a la par que elegante. Y por si
queremos comprobar otros nodos, podemos pasarle un parámetro.
Eso sí, tened en cuenta que la lista
de nombres de etiquetas no es exhaustiva. Seguro que alguna se me
olivda.
Pasemos ahora al que quizá sea el
método más complejo de la clase: “deleteTags”.
Nosotros le pasamos un rango y él
elimina los nodos correspondientes a ese rango. Vaya primero el
código
def
deleteTags(range) if range.count == 0 return end
doc_range = (0..(@nodes.length - 1)) if not
doc_range.include?(range.first) or not
doc_range.include?(range.last) raise 'Out of range'
end # Delete range @nodes.slice!(range)
#
Fix top document count document = @type == :document ? self :
@document document.ends_at = @nodes.length - 1 #
Fix nodes created from top document
document.nodes_created.each do |n|
if n.starts_at >=
range.first if n.starts_at <= range.last #
Object is inside deleted range. Clear it n.clear
else # Object is outside deleted range. Keep it, but...
# Compute new start and end nodes n.starts_at -=
range.count n.ends_at -= range.count end
elsif n.ends_at >= range.first if n.ends_at <=
range.last # "Non well formed" HTML: trim
elements n.ends_at = range.first - 1 if n.ends_at
< 0 n.clear end else # The
deleted range is inside the node # Compute new end node
n.ends_at -= range.count end end end
end |
La primera parte del método hace unas
cuantas comprobaciones y elimina los nodos:
...
...
if
not doc_range.include?(range.first) or not
doc_range.include?(range.last) raise 'Out of range'
end # Delete range @nodes.slice!(range) |
El método “slice!” elimina un
rango de elementos de un array. Y, por si necesitamos saber qué
había en los elementos eliminados, retorna un array con lo que se ha
cargado. Existe un método parecido llamado “slice” que funciona
de forma similar, pero sin borrar nada del array original.
Hasta aquí todo bien... excepto por
los “efectos laterales”. Vamos a ver: imaginemos que hemos ido
creando elementos a partir del documento con llamadas, por ejemplo, a
“getElementById”. Al quitar código HTML de nuestro documento
puede que algunos de estos elementos desaparezcan. O que varíen sus
posiciones de inicio o final, puesto que hemos quitado cosas.
Así que ahora tenemos que arreglar el
desaguisado. Comencemos por el documento padre, el original.
El objeto que invoca el método puede
ser o no el documento original, así que primero lo comprobamos:
document
= @type == :document ? self : @document |
… y corregimos su atributo @ends_at
document.ends_at
= @nodes.length - 1 |
Recuerda que podemos hacer esto porque
el accessor a @ends_at es de tipo “protegido”.
Y ahora le damos un repaso a todos los
nodos creados a partir del documento. “document”, el objeto que
lo representa los almacena en un atributo llamado “nodes_create”.
Así que se hace un bucle sobre ellos con:
document.nodes_created.each
do |n|
...
... ...
end |
Dentro de este bucle, para cada nodo
“n” realizamos las siguientes comprobaciones:
- Si el inicio del nodo está dentro del rango del nodo, eliminamos dicho nodo mediante una llamada a “clear”. Y a otra cosa, mariposa.
Si el objeto comienza después del
final del rango eliminado, tenemos que restar de sus índice de
inicio y finalización el número de nodos que hemos borrado.
Si el objeto comienza antes del
inicio del rango borrado y acaba dentro de él, habrá que corregir
su índice de finalización y decir que acaba justo antes del rango
que acabamos de eliminar.
Si el objeto comienza antes del
inicio del rango eliminado y acaba fuera de él, o sea si el objeto
contiene dentro el rango eliminado, habrá que corregir sólo su
índice de finalización, decrementándolo en el número de nodos
eliminados.
O, dicho en Rubyense:
if
n.starts_at >= range.first if n.starts_at <=
range.last # Object is inside deleted range. Clear it
n.clear else # Object is outside deleted range.
Keep it, but... # Compute new start and end nodes
n.starts_at -= range.count n.ends_at -= range.count
end elsif n.ends_at >= range.first if n.ends_at <=
range.last # "Non well formed" HTML: trim
elements n.ends_at = range.first - 1 if n.ends_at
< 0 n.clear end else # The
deleted range is inside the node # Compute new end node
n.ends_at -= range.count end end
|
Añadir nodos es mucho más sencillo
que borrarlos. La única corrección necesaria consiste en
incrementar los inicios y finales de nodos que se produzcan después
del punto en que insertamos el nuevo contenido. Eso es lo que hace
“inserTags”, que añade en una posición dada un conjunto de
nodos o etiquetas:
def
insertTags(position,tags) # Add the new elements
if position >= @nodes.length # Append
@nodes.concat(tags) elsif position >= 0
@nodes.insert(position, tags).flatten! else raise
'Invalid position' end # Fix top document indexes
document = typeOf == :document ? self : @document
document.ends_at += tags.length # Fix nodes created
from top document document.nodes_created.each do |n| if
n.starts_at >=position n.starts_at += tags.length
end
if n.ends_at >=position n.ends_at +=
tags.length end end end |