/*
ItsNat Java Web Application Framework
Copyright (C) 2007-2011 Jose Maria Arranz Santamaria, Spanish citizen
This software is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 3 of
the License, or (at your option) any later version.
This software is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details. You should have received
a copy of the GNU Lesser General Public License along with this program.
If not, see <http://www.gnu.org/licenses/>.
*/
package org.itsnat.impl.core.template;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;
import org.itsnat.core.ItsNatException;
import org.itsnat.core.domutil.ItsNatTreeWalker;
import org.itsnat.impl.comp.mgr.ItsNatStfulDocComponentManagerImpl;
import org.itsnat.impl.core.domutil.DOMUtilInternal;
import org.itsnat.impl.core.domutil.NamespaceUtil;
import org.itsnat.impl.core.markup.render.DOMRenderImpl;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.Text;
import org.w3c.dom.html.HTMLElement;
import org.w3c.dom.html.HTMLTableElement;
/**
*
* @author jmarranz
*/
public abstract class StfulTemplateVersionDelegateImpl extends MarkupTemplateVersionDelegateImpl
{
public static final Set<String> HTML_ELEMS_NOT_USE_CHILD_TEXT = new HashSet<String>();
public static final Set<String> HTML_ELEMS_NOT_VALID_CHILD_COMMENT = new HashSet<String>();
static
{
// Elementos que pueden tener child nodes tipo Text pero que son in�tiles
// funcional y visualmente, es decir como mucho pueden tener espacios/fines de l�nea.
// De esta manera aceleramos y reducimos el consumo de memoria
// En alg�n caso es adem�s imprescindible por ejemplo los nodos
// bajo <tr> debido a que cuando
// se a�aden via JavaScript en FireFox (creo que v2) existe un bug en el que
// indenta visualmente este nodo de texto como si fuera un <td>
// (aunque no aparezca en el DOM como td)
HTML_ELEMS_NOT_USE_CHILD_TEXT.add("dl");
HTML_ELEMS_NOT_USE_CHILD_TEXT.add("frameset");
HTML_ELEMS_NOT_USE_CHILD_TEXT.add("head");
HTML_ELEMS_NOT_USE_CHILD_TEXT.add("html");
HTML_ELEMS_NOT_USE_CHILD_TEXT.add("map");
HTML_ELEMS_NOT_USE_CHILD_TEXT.add("menu");
HTML_ELEMS_NOT_USE_CHILD_TEXT.add("ol");
HTML_ELEMS_NOT_USE_CHILD_TEXT.add("optgroup");
HTML_ELEMS_NOT_USE_CHILD_TEXT.add("select");
HTML_ELEMS_NOT_USE_CHILD_TEXT.add("table");
HTML_ELEMS_NOT_USE_CHILD_TEXT.add("tbody");
HTML_ELEMS_NOT_USE_CHILD_TEXT.add("tfoot");
HTML_ELEMS_NOT_USE_CHILD_TEXT.add("thead");
HTML_ELEMS_NOT_USE_CHILD_TEXT.add("tr");
HTML_ELEMS_NOT_USE_CHILD_TEXT.add("ul");
// Elementos que NO pueden tener comentarios como hijo:
// Los comentarios bajo <html> son llevados bajo <head> en Firefox 3
// Los comentarios bajo DL, OL o UL son problem�ticos en IE 6 pues
// los mete en el elemento hijo m�s cercano, por ejemplo un comentario
// bajo OL lo mete en el LI m�s cercano.
// Los comentarios bajo SELECT son filtrados en IE 6, no merece la pena
// una posible reinserci�n pues como estos problemas ocurren en IE 6 y este navegador ha sido (y es) muy popular
// es alt�simamente improbable que una p�gina web necesite dichos comentarios
// para algo en el cliente si se quiere que funcione en el IE 6.
// Los comentarios bajo elementos tal y como TABLE, TBODY, TFOOT, THEAD y TR
// no dan problemas y no son filtrados en IE 6 pero he descubierto que su presencia
// en la construcci�n de la table con JavaScript (por ejemplo en carga de trees basados en tables con fastLoad = false)
// pueden ser problem�tica (provoca errores raros), aparte de impedir
// el uso de innerHTML (el innerHTML filtra los comentarios), en general el TABLE en MSIE 6 es muy delicado
// y esos comentarios no aportan nada.
// Hay navegadores tal y como BlackBerryOld y S60WebKit FP 1 que filtran todos
// los comentarios en tiempo de carga, este problema es abordado espec�ficamente
// reinsertando dichos comentarios.
// FRAMESET y OPTGROUP no han sido testeados pero por si acaso los incluimos.
HTML_ELEMS_NOT_VALID_CHILD_COMMENT.add("frameset");
HTML_ELEMS_NOT_VALID_CHILD_COMMENT.add("html");
HTML_ELEMS_NOT_VALID_CHILD_COMMENT.add("dl");
HTML_ELEMS_NOT_VALID_CHILD_COMMENT.add("ol");
HTML_ELEMS_NOT_VALID_CHILD_COMMENT.add("optgroup");
HTML_ELEMS_NOT_VALID_CHILD_COMMENT.add("select");
HTML_ELEMS_NOT_VALID_CHILD_COMMENT.add("table");
HTML_ELEMS_NOT_VALID_CHILD_COMMENT.add("td");
HTML_ELEMS_NOT_VALID_CHILD_COMMENT.add("tbody");
HTML_ELEMS_NOT_VALID_CHILD_COMMENT.add("ul");
}
/**
* Creates a new instance of OtherNSTemplateVersionDelegateImpl
*/
public StfulTemplateVersionDelegateImpl(MarkupTemplateVersionImpl parent)
{
super(parent);
}
@Override
public boolean declaredAsComponent(Element elem)
{
boolean decAsComp = super.declaredAsComponent(elem);
if (decAsComp) return true;
return ItsNatStfulDocComponentManagerImpl.declaredAsHTMLComponent(elem);
}
@Override
public void serializeNode(Node node,Writer out,DOMRenderImpl nodeRender)
{
if (is_SCRIPT_or_STYLE_Text(node))
serialize_SCRIPT_or_STYLE_Text((Text)node,out,nodeRender);
else
super.serializeNode(node,out,nodeRender);
}
public static boolean is_SCRIPT_or_STYLE_Text(Node node)
{
// No solo HTML, pueden ser de SVG o XUL
if (node.getNodeType() != Node.TEXT_NODE)
return false;
Node parent = node.getParentNode();
if (parent == null) return false;
if (parent.getNodeType() != Node.ELEMENT_NODE)
return false;
Element parentElem = (Element)parent;
String localName = parentElem.getLocalName();
if (localName.equals("script"))
return true;
if (localName.equals("style"))
return true;
return false;
}
public void serialize_SCRIPT_or_STYLE_Text(Text content,Writer out,DOMRenderImpl nodeRender)
{
// Los elementos <script> y <style> son tratados
// de forma especial tal que su contenido es SIEMPRE un �nico nodo Text hijo
// aunque contengan comentarios <!-- --> o <![CDATA[ ]]>
// Los comentarios y CDATA fuera de <script> y <style> son creados en DOM normalmente.
// El problema es que al pasar al DOMRenderImpl
// el nodo Text, el render no comprueba que es hijo de <script> o <style>
// y el texto lo trata normalmente convirtiendo los < y > en < y gt;
// fastidiando en el cliente dichos elementos.
// Sin embargo cuando se env�a el propio <script> o <style> el render
// sabe que tiene que tratar de forma especial el contenido de ambos respetando
// los comentarios y los CDATA.
// Por tanto serializamos el elemento padre tambi�n y luego quitamos los tag de inicio y final
// Esto tambi�n es aplicable a SVG y XUL pues ambos pueden ser introducidos
// como elementos HTML pero tambi�n con otros namespaces, por ejemplo
// <script> existe nativamente en (XUL) o bien a�adido via xlink namespace (SVG).
// El <style> por otra parte forma parte de SVG
Element parentElem = (Element)content.getParentNode();
StringWriter strWriter = new StringWriter();
super.serializeNode(parentElem,strWriter,nodeRender);
String code = strWriter.toString();
// Quitamos el tag de abrir y cerrar <tag>...</tag>
int pos = code.indexOf('>');
code = code.substring(pos + 1); // quitamos <tag>
pos = code.lastIndexOf('<');
code = code.substring(0,pos); // quitamos </tag>
try
{
out.write(code);
}
catch(IOException ex)
{
throw new ItsNatException(ex);
}
}
@Override
public void normalizeDocument(Document doc)
{
super.normalizeDocument(doc);
cleanDocumentChildren(doc);
normalizeHTMLElements(doc);
}
protected static void cleanDocumentChildren(Document doc)
{
/* Eliminamos los nodos bajo el Document que no sean
* el DocumentType, processing instructions (�til en XUL), el root (<html>) y nodos de texto, por ejemplo comentarios.
* La raz�n NO es por un problema de c�lculo de paths pues el c�lculo
* de paths empieza desde <html> (usando "de") y es rar�simo que queramos
* acceder a los elementos de esa zona, tampoco el problema es los nodos
* de texto (espacios y fines de l�nea) que por ejemplo son filtrados en FireFox,MSIE 6, Safari 3 y Opera 9
* El problema son los comentarios, no tanto porque son filtrados por FireFox por ejemplo,
* el problema es S40WebKit por ejemplo que NO lo filtra, lo inserta BAJO <head>
* distorsionando el sistema de paths y FireFox bajo <html>.
*
* Por otra parte en Opera (en HTML, no XHTML o XML) y Safari 3
* no incluyen en el document.childNodes el DocumentType por lo que
* tenemos que a�adirlo en JavaScript y con comentarios no sabr�amos
* cual es el orden de inserci�n respecto a los mismos.
*
* Esto es aplicable a SVG aunque S40WebKit no tenga soporte de SVG
*
* Tambi�n lo hacemos para XML, en general suena raro tener comentarios
*
*/
Node node = doc.getFirstChild();
while (node != null)
{
int type = node.getNodeType();
if ((type != Node.ELEMENT_NODE) &&
(type != Node.DOCUMENT_TYPE_NODE) &&
(type != Node.TEXT_NODE) &&
(type != Node.PROCESSING_INSTRUCTION_NODE) )
{
Node next = node.getNextSibling();
doc.removeChild(node);
node = next;
}
else
{
node = node.getNextSibling();
}
}
}
public static void normalizeHTMLElements(Document doc)
{
// Buscamos que el DOM del navegador sea id�ntico al del servidor,
// los navegadores a veces hacen modificaciones por su cuenta
// En teor�a este m�todo se llama antes de que haya mutation listeners
// asociados.
// Firefox y MSIE a�aden autom�ticamente TBODY cuando se inserta
// un "<table><tr>..." serializado (no lo hace si es por DOM)
// por eso lo a�adimos nosotros porque sino los paths fallan
LinkedList<Node> htmlTables = DOMUtilInternal.getChildElementListWithTagNameNS(doc,NamespaceUtil.XHTML_NAMESPACE,"table", true);
if (htmlTables != null)
{
for(Iterator<Node> it = htmlTables.iterator(); it.hasNext(); )
{
HTMLTableElement table = (HTMLTableElement)it.next();
boolean hasTBody = (ItsNatTreeWalker.getFirstChildElementWithTagNameNS(table,NamespaceUtil.XHTML_NAMESPACE,"tbody") != null);
if (!hasTBody)
{
// No tiene TBODY, a�adimos (suponemos que tampoco hay un THEAD etc)
HTMLElement tbody = (HTMLElement)doc.createElementNS(NamespaceUtil.XHTML_NAMESPACE,"tbody");
// Soportamos la existencia de COLGROUP antes del primer TR, a partir del primer TR
// copiaremos todos los nodos que siguen al primer TR bajo TBODY
Node child = ItsNatTreeWalker.getFirstChildElementWithTagNameNS(table,NamespaceUtil.XHTML_NAMESPACE,"tr");
while(child != null)
{
Node next = child.getNextSibling();
tbody.appendChild(child); // lo quita tambi�n de table
child = next;
}
table.appendChild(tbody);
}
}
}
for(String localName : HTML_ELEMS_NOT_USE_CHILD_TEXT)
{
removeUnusefulHTMLChildTextNodes(localName,doc);
}
for(String localName : HTML_ELEMS_NOT_VALID_CHILD_COMMENT)
{
removeUnusefulHTMLChildComments(localName,doc);
}
}
public static void removeUnusefulHTMLChildTextNodes(String localName,Document doc)
{
LinkedList<Node> elements = DOMUtilInternal.getChildElementListWithTagNameNS(doc,NamespaceUtil.XHTML_NAMESPACE,localName,true);
if (elements != null)
{
for(Iterator<Node> it = elements.iterator(); it.hasNext(); )
{
Element elem = (Element)it.next();
DOMUtilInternal.removeAllUnusefulChildTextNodes(elem);
}
}
}
public static void removeUnusefulHTMLChildComments(String localName,Document doc)
{
LinkedList<Node> elements = DOMUtilInternal.getChildElementListWithTagNameNS(doc,NamespaceUtil.XHTML_NAMESPACE,localName,true);
if (elements != null)
{
for(Iterator<Node> it = elements.iterator(); it.hasNext(); )
{
Element elem = (Element)it.next();
DOMUtilInternal.removeAllDirectChildComments(elem);
}
}
}
}