Package org.itsnat.impl.core.resp.shared.html

Source Code of org.itsnat.impl.core.resp.shared.html.ResponseDelegateHTMLLoadDocFixFilteredCommentsImpl

/*
  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.resp.shared.html;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
import org.itsnat.core.domutil.ItsNatTreeWalker;
import org.itsnat.impl.core.clientdoc.ClientDocumentStfulImpl;
import org.itsnat.impl.core.doc.ItsNatHTMLDocumentImpl;
import org.itsnat.impl.core.domutil.DOMUtilHTML;
import org.itsnat.impl.core.domutil.DOMUtilInternal;
import org.itsnat.impl.core.jsren.JSRenderImpl;
import org.itsnat.impl.core.domutil.NamespaceUtil;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.html.HTMLDocument;
import org.w3c.dom.html.HTMLElement;
import org.w3c.dom.html.HTMLHeadElement;

/**
*
* @author jmarranz
*/
public class ResponseDelegateHTMLLoadDocFixFilteredCommentsImpl
{
    protected static final String FAKE_COMMENT_NAME_VALUE = "itsnatFakeCommName";
    protected static final String FAKE_COMMENT_ID_VALUE = "itsnatFakeCommId_";
    protected static final String PARENT_COMMENT_ID_ATTR_NAME = "idItsNatFakeCommParent";
    protected static final String NEXT_COMMENT_ID_ATTR_NAME = "idItsNatFakeCommPrevious";

    public static final Set<String> ELEMENTS_NOT_VALID_CHILD_COMMENT = new HashSet<String>();

    static
    {
        // Elementos que NO pueden tener elementos como hijo diferentes
        // a unos concretos y por tanto en donde no funcionar�a el uso de
        // <span> o <style>

        // Es el caso de BlackBerry y S60WebKit FP 1 en donde los comentarios son siempre filtrados
        // en carga, por ello para conservarlos los substituimos temporalmente por <style> bajo <head> y <span> bajo <body>
        // pero este <span> es TAMBIEN filtrado en algunos elementos cuyos elementos hijos est�n
        // predeterminados. Aunque s�lo
        // he estudiado el problema en TABLE, TBODY, TFOOT, THEAD y TR es razonable
        // que un <span> no puede ser hijo de elementos que s�lo admiten
        // ciertos tipos de elementos como hijo, por ejemplo un <span> bajo un <table>.
        // En algunos de estos elementos los comentarios son filtrados ya a nivel
        // de servidor porque son problem�ticos por ejemplo para el IE 6 sin "workaround".
        // proporcionado por ItsNat, por lo que no se dar� el caso de necesitar reinserci�n
        // (pero por si acaso los incluimos aqu�).

        ELEMENTS_NOT_VALID_CHILD_COMMENT.add("dl");
        ELEMENTS_NOT_VALID_CHILD_COMMENT.add("frameset");
        ELEMENTS_NOT_VALID_CHILD_COMMENT.add("html");
        ELEMENTS_NOT_VALID_CHILD_COMMENT.add("ol");
        ELEMENTS_NOT_VALID_CHILD_COMMENT.add("optgroup");
        ELEMENTS_NOT_VALID_CHILD_COMMENT.add("select");
        ELEMENTS_NOT_VALID_CHILD_COMMENT.add("table");
        ELEMENTS_NOT_VALID_CHILD_COMMENT.add("tbody");
        ELEMENTS_NOT_VALID_CHILD_COMMENT.add("tfoot");
        ELEMENTS_NOT_VALID_CHILD_COMMENT.add("thead");
        ELEMENTS_NOT_VALID_CHILD_COMMENT.add("tr");
        ELEMENTS_NOT_VALID_CHILD_COMMENT.add("ul");
    }

    protected LinkedList<Comment> comments = new LinkedList<Comment>();
    protected LinkedList<Node[]> fakeCommentList = new LinkedList<Node[]>();
    protected LinkedList<Node[]> holdCommentList = new LinkedList<Node[]>();
    protected ResponseDelegateHTMLLoadDocImpl responseParent;

    /**
     * Creates a new instance of ResponseDelegateHTMLLoadDocFixFilteredCommentsImpl
     */
    public ResponseDelegateHTMLLoadDocFixFilteredCommentsImpl(ResponseDelegateHTMLLoadDocImpl responseParent)
    {
        this.responseParent = responseParent;
    }

    public ClientDocumentStfulImpl getClientDocumentStful()
    {
        return responseParent.getClientDocumentStful();
    }

    public void preSerializeDocument()
    {
        // En este caso la estrategia es la siguiente: el DOM del documento
        // tiene nodos cacheados,
        // dichos nodos cacheados pueden tener comentarios que ser�n filtrados
        // en el cliente, sin embargo esos comentarios NO nos interesan pues
        // no se reflejan en el DOM del servidor por tanto no ser�n accedidos ni habr� c�lculo
        // de paths ni path-ids de los mismos
        ItsNatHTMLDocumentImpl itsNatDoc = responseParent.getItsNatHTMLDocument();
        HTMLDocument doc = itsNatDoc.getHTMLDocument();

        replaceComments(doc);
    }

    public String postSerializeDocument(String docMarkup)
    {
        if (!comments.isEmpty())
        {
            restoreComments();

            addCodeToSendComments();
        }

        return docMarkup;
    }

    protected boolean replaceComments(HTMLDocument doc)
    {
        // Tenemos la garant�a de que no hay comentarios en lugares donde
        // no puede ponerse un <style> bajo <head> y <span> bajo BODY pues fueron filtrados en la normalizaci�n.
        // El S60WebKit (FP 1) mueve los <style> bajo <body> a <head>
        // Sabemos que en este contexto los mutation events est�n desactivados

        HTMLHeadElement head = DOMUtilHTML.getHTMLHead(doc);
        replaceComments(head,"style");
        HTMLElement body = doc.getBody();
        replaceComments(body,"span");

        return !comments.isEmpty(); // Si false es que no hay ningun comentario, no se ha hecho nada
    }

    protected Node replaceComments(Node node,String localNameReplacing)
    {
        if (node.getNodeType() == Node.COMMENT_NODE)
        {
            Node nodeRes = node; // En un caso node es reemplazado por otro
           
            int index = comments.size(); // Antes de a�adir, empezamos en cero
            Element parentNode = (Element)node.getParentNode();
            String idCommentValue = FAKE_COMMENT_ID_VALUE + index;
            if ((parentNode instanceof HTMLElement) &&
                 ELEMENTS_NOT_VALID_CHILD_COMMENT.contains(parentNode.getLocalName()))
            {
                Element targetNode;
                String attrName;
                Element nextNode = ItsNatTreeWalker.getNextSiblingElement(node);
                if (nextNode != null)
                {
                    targetNode = nextNode;
                    attrName = NEXT_COMMENT_ID_ATTR_NAME;
                }
                else
                {
                    targetNode = parentNode;
                    attrName = PARENT_COMMENT_ID_ATTR_NAME;
                }
                // Puede darse el caso de varios comentarios seguidos, por lo que a�adimos los ids separados por comas
                String attrValue = targetNode.getAttribute(attrName);
                if (attrValue.equals("")) attrValue = idCommentValue;
                else attrValue = attrValue + "," + idCommentValue;
                DOMUtilInternal.setAttribute(targetNode,attrName,attrValue);
                holdCommentList.add(new Node[]{node,targetNode});
            }
            else
            {
                Document doc = node.getOwnerDocument();
                Element fakeComm = doc.createElementNS(NamespaceUtil.XHTML_NAMESPACE,localNameReplacing);
                DOMUtilInternal.setAttribute(fakeComm,"name",FAKE_COMMENT_NAME_VALUE);
                DOMUtilInternal.setAttribute(fakeComm,"id"  ,idCommentValue);
                parentNode.replaceChild(fakeComm, node);
                fakeCommentList.add(new Node[]{node,fakeComm});
                nodeRes = fakeComm;  // Ha sido reemplazado
            }
            comments.add((Comment)node);

            return nodeRes;
        }
        else
        {
            if (!node.hasChildNodes()) return node;

            Node child = node.getFirstChild();
            while(child != null)
            {
                // Hay que tener en cuenta que replaceComments reemplaza el nodo child si es un comentario
                // por otro               
                child = replaceComments(child,localNameReplacing);

                child = child.getNextSibling();
            }
           
            return node;
        }
   }


    protected void restoreComments()
    {
        if (comments.isEmpty()) return;

        // Restauramos el estado anterior del DOM
        if (!fakeCommentList.isEmpty())
        {
            for(Node[] pair : fakeCommentList)
            {
                Comment comm = (Comment)pair[0];
                Element fakeComm = (Element)pair[1];
                fakeComm.getParentNode().replaceChild(comm,fakeComm);
            }
        }

        if (!holdCommentList.isEmpty())
        {
            for(Node[] pair : holdCommentList)
            {
                //Comment comm = (Comment)pair[0];
                Element container = (Element)pair[1];
                container.removeAttribute(NEXT_COMMENT_ID_ATTR_NAME);
                container.removeAttribute(PARENT_COMMENT_ID_ATTR_NAME);
            }
        }
    }

    protected void addCodeToSendComments()
    {
        ItsNatHTMLDocumentImpl itsNatDoc = responseParent.getItsNatHTMLDocument();
        ClientDocumentStfulImpl clientDoc = getClientDocumentStful();

        StringBuilder code = new StringBuilder();

        // Metemos una funci�n para evitar dejar variables globales
        code.append("var func = function()\n");
        code.append("{\n");
        code.append("  var commMap = new Object();\n");

    int i = 0;
    for(Comment comm : comments)
    {
        String text = comm.getData();
        // Sabemos que text es posible que contenga un ${} indicando que el comentario
        // fue cacheado, "descacheamos" aqu�.
        text = itsNatDoc.resolveCachedNodes(text,false); // La verdad es que el resolveEntities da igual en este caso
        code.append("  commMap[\"" + (FAKE_COMMENT_ID_VALUE + i) + "\"] = " + JSRenderImpl.toTransportableStringLiteral(text,clientDoc.getBrowser()) + ";\n");
        i++;
    }

    if (!fakeCommentList.isEmpty())
    {
        // Resolvemos los comentarios que fueron substituidos por elemento "fake comment"
        code.append("  var fakeNodeList = document.getElementsByName(\"" + FAKE_COMMENT_NAME_VALUE + "\");\n");
        // Hacemos una copia porque un NodeList seg�n el est�ndar es una colecci�n viva por tanto
        // si quitamos los elementos del documento puede cambiar (de hecho es as� en S60WebKit 5th v0.9)
        // y no queremos eso.
        code.append("  var len = fakeNodeList.length;\n");
        code.append("  var fakeNodeArr = new Array(len);\n");
        code.append("  for(var i=0;i < len; i++) fakeNodeArr[i] = fakeNodeList[i];\n");
        code.append("  fakeNodeList = null;\n"); // para ahorrar memoria
        code.append("  for(var i=0;i < len; i++)\n");
        code.append("  {\n");
        code.append("    var fakeCom = fakeNodeArr[i];\n");
        code.append("    var text = commMap[fakeCom.id];\n");
        code.append("    var comm = document.createComment(text);\n");
        code.append("    fakeCom.parentNode.replaceChild(comm,fakeCom);\n");
        code.append("  }\n");
        code.append("  fakeNodeArr = null;\n"); // para ahorrar memoria
    }

    if (!holdCommentList.isEmpty())
    {
        // Ahora los que fueron "recordados" a trav�s de ids especiales
        // en el elemento padre o en el next
        // Insertaremos los comentarios en la misma zona seguidos, seguramente no coincidir�
        // con la posici�n respecto a los nodos de texto (si no son filtrados) pero dichos nodos
        // de texto son espacios y/o fines de l�nea que no influyen en el c�lculo
        // de paths y es absurdo necesitar acceder a ellos desde el servidor (no valen para nada por eso
        // suelen ser filtrados).

        // Necesitamos esta funci�n getNextNode para iterar (obtenida de ItsNatTreeWalker)
        code.append("  function getNextNode(node)\n");
        code.append("  {\n");
        code.append("    if (node == null) return null;\n");
        code.append("    var result = node.firstChild;\n");
        code.append("    if (result != null) return result;\n");
        code.append("    result = node.nextSibling;\n");
        code.append("    if (result != null) return result;\n");
        code.append("    var parentNode = node.parentNode;\n");
        code.append("    while (parentNode != null)\n");
        code.append("    {\n");
        code.append("      result = parentNode.nextSibling;\n");
        code.append("      if (result != null) return result;\n");
        code.append("      else parentNode = parentNode.parentNode;\n");
        code.append("    }\n");
        code.append("    return null;\n");
        code.append("  }\n");

        code.append("  function addComments(node,name,value,isNext,commMap)\n");
        code.append("  {\n");
        code.append("    var nextNode, parentNode;\n");
        code.append("    if (isNext) { nextNode = node; parentNode = node.parentNode; }\n");
        code.append("    else { nextNode = null; parentNode = node; }\n");
        code.append("    var idCommList = value.split(\",\");\n");
        code.append("    for(var i = 0; i < idCommList.length; i++)\n");
        code.append("    {\n");
        code.append("      var text = commMap[idCommList[i]];\n");
        code.append("      var comm = document.createComment(text);\n");
        code.append("      parentNode.insertBefore(comm,nextNode);\n");
        code.append("    }\n");
        code.append("    node.removeAttribute(name);\n"); // Ya no se necesita
        code.append("  }\n");

        code.append("  var node = document;\n");
        code.append("  do \n");
        code.append("  {\n");
        code.append("    if (node.nodeType == 1)\n");
        code.append("    {\n"); // Un mismo nodo puede ser referencia tanto padre como next
        code.append("      var name,value;\n");
        code.append("      name = \"" + NEXT_COMMENT_ID_ATTR_NAME + "\";\n");
        code.append("      value = node.getAttribute(name);\n");
        code.append("      if ((value!=null)&&(value!=\"\")) addComments(node,name,value,true,commMap);\n");
        code.append("      name = \"" + PARENT_COMMENT_ID_ATTR_NAME + "\";\n");
        code.append("      value = node.getAttribute(name);\n");
        code.append("      if ((value!=null)&&(value!=\"\")) addComments(node,name,value,false,commMap);\n");
        code.append("    }\n");
        code.append("    node = getNextNode(node);\n");
        code.append("  }\n");
        code.append("  while(node != null);\n");
    }

        code.append("};\n");
        code.append("func();\n");
        code.append("func = null;\n"); // No se necesita m�s, para liberar memoria

        // Insertamos AL PRINCIPIO para que el c�digo generado por el usuario (que va despu�s)
        // vea ya los comentarios como comentarios.
        responseParent.addFixDOMCodeToSend(code.toString());
    }
}
TOP

Related Classes of org.itsnat.impl.core.resp.shared.html.ResponseDelegateHTMLLoadDocFixFilteredCommentsImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.