001: /*
002: ItsNat Java Web Application Framework
003: Copyright (C) 2007 Innowhere Software Services S.L., Spanish Company
004: Author: Jose Maria Arranz Santamaria
005:
006: This program is free software: you can redistribute it and/or modify
007: it under the terms of the GNU Affero General Public License as published by
008: the Free Software Foundation, either version 3 of the License, or
009: (at your option) any later version. See the GNU Affero General Public
010: License for more details. See the copy of the GNU Affero General Public License
011: included in this program. If not, see <http://www.gnu.org/licenses/>.
012: */
013:
014: package org.itsnat.impl.core.html;
015:
016: import org.itsnat.core.domutil.ItsNatTreeWalker;
017: import org.itsnat.impl.comp.html.ItsNatHTMLComponentManagerImpl;
018: import org.itsnat.impl.core.DocFragmentTemplateVersionImpl;
019: import org.itsnat.impl.core.MarkupTemplateVersionDelegateImpl;
020: import org.itsnat.impl.core.MarkupTemplateVersionImpl;
021: import org.itsnat.impl.core.dom.parse.HTMLParserUtil;
022: import org.itsnat.impl.core.dom.render.DOMRenderImpl;
023: import org.itsnat.impl.core.http.ItsNatImpl;
024: import org.itsnat.impl.core.js.domrender.node.JSHTMLElementRenderImpl;
025: import org.itsnat.impl.core.domutil.ItsNatDOMUtilInternal;
026: import org.itsnat.impl.core.js.domrender.node.JSNodeRenderImpl;
027: import org.w3c.dom.Document;
028: import org.w3c.dom.DocumentFragment;
029: import org.w3c.dom.Element;
030: import org.w3c.dom.Node;
031: import org.w3c.dom.NodeList;
032: import org.w3c.dom.Text;
033: import org.w3c.dom.html.HTMLDocument;
034: import org.w3c.dom.html.HTMLElement;
035: import org.w3c.dom.html.HTMLOListElement;
036: import org.w3c.dom.html.HTMLTableElement;
037: import org.w3c.dom.html.HTMLTableRowElement;
038: import org.w3c.dom.html.HTMLUListElement;
039: import org.xml.sax.InputSource;
040:
041: /**
042: *
043: * @author jmarranz
044: */
045: public class HTMLTemplateVersionDelegateImpl extends
046: MarkupTemplateVersionDelegateImpl {
047:
048: /**
049: * Creates a new instance of HTMLTemplateVersionDelegateImpl
050: */
051: public HTMLTemplateVersionDelegateImpl(
052: MarkupTemplateVersionImpl parent) {
053: super (parent);
054: }
055:
056: public Document parseDocument(InputSource input, String encoding) {
057: return HTMLParserUtil.parse(input, encoding);
058: }
059:
060: public String serializeNode(Node node) {
061: if (is_SCRIPT_or_STYLE_Content(node))
062: return serialize_SCRIPT_or_STYLE_Content((Text) node);
063:
064: return super .serializeNode(node);
065: }
066:
067: public static boolean is_SCRIPT_or_STYLE_Content(Node node) {
068: if (node.getNodeType() != Node.TEXT_NODE)
069: return false;
070:
071: Node parent = node.getParentNode();
072: if (parent == null)
073: return false;
074:
075: if (parent.getNodeType() != node.ELEMENT_NODE)
076: return false;
077:
078: Element parentElem = (Element) parent;
079: String tagNameUpper = parentElem.getTagName();
080: if (tagNameUpper.equals("SCRIPT")) // Xerces HTML DOM devuelve siempre en mayúsculas
081: return true;
082:
083: if (tagNameUpper.equals("STYLE"))
084: return true;
085:
086: return false;
087: }
088:
089: public String serialize_SCRIPT_or_STYLE_Content(Text content) {
090: // Todo esto es por culpa del NekoHTML (o quizás del HTML DOM del Xerces)
091: // el caso es que los elementos <script> y <style> son tratados
092: // de forma especial tal que su contenido es SIEMPRE un único nodo Text hijo
093: // aunque contengan comentarios <!-- --> o <![CDATA[ ]]>
094: // Los comentarios y CDATA fuera de <script> y <style> son creados en DOM normalmente.
095: // El problema es que al pasar al DOMRenderImpl (Xalan detrás)
096: // el nodo Text, el render no comprueba que es hijo de <script> o <style>
097: // y el texto lo trata normalmente convirtiendo los < y > en < y gt;
098: // fastidiando en el cliente dichos elementos.
099: // Sin embargo cuando se envía el propio <script> o <style> el render
100: // sabe que tiene que tratar de forma especial el contenido de ambos respetando
101: // los comentarios y los CDATA.
102: // Por tanto serializamos el elemento padre también y luego quitamos los tag de inicio y final
103:
104: DOMRenderImpl nodeRender = getMarkupTemplateVersion()
105: .getNodeDOMRender();
106:
107: Element parentElem = (Element) content.getParentNode();
108:
109: String code = nodeRender.serializeNode(parentElem);
110: // Quitamos el tag de abrir y cerrar <tag>...</tag>
111: int pos = code.indexOf('>');
112: code = code.substring(pos + 1); // quitamos <tag>
113: pos = code.lastIndexOf('<');
114: code = code.substring(0, pos); // quitamos </tag>
115: return code;
116: }
117:
118: public DocumentFragment loadDocumentFragmentIncluded(
119: DocFragmentTemplateVersionImpl docFragTemplateIncVersion,
120: Element includeElem) {
121: MarkupTemplateVersionImpl template = getMarkupTemplateVersion();
122: if (docFragTemplateIncVersion instanceof HTMLDocFragmentTemplateVersionImpl) {
123: HTMLDocFragmentTemplateVersionImpl htmlDocFragDescIncVer = (HTMLDocFragmentTemplateVersionImpl) docFragTemplateIncVersion;
124: if (ItsNatDOMUtilInternal.isHeadChild(includeElem)) // El elemento a substituir está en el <head>
125: return htmlDocFragDescIncVer
126: .loadDocumentFragmentHead(template);
127: else
128: return htmlDocFragDescIncVer
129: .loadDocumentFragmentBody(template);
130: } else
131: return docFragTemplateIncVersion
132: .loadDocumentFragment(template); // Ejemplo posible: insertando un trozo de SVG en un documento/fragmento HTML
133: }
134:
135: public boolean declaredAsComponent(Element elem) {
136: return ItsNatHTMLComponentManagerImpl
137: .declaredAsHTMLComponent(elem);
138: }
139:
140: public boolean isElementNotCacheableByNamespace(Element elem) {
141: // La razón de esto es la siguiente: un element cacheado contiene un único nodo
142: // de texto con la marca/variable de caché, este tipo de nodo es candidato a ser
143: // enviado al cliente via JavaScript como innerHTML, de hecho DEBE enviarse con innerHTML
144: // pues via DOM normal NO funciona pues el nodo
145: // de texto NO es realmente de texto (habría que reconstruir el DOM del nodo cacheado),
146: // por tanto la única forma de enviar al cliente con JavaScript un nodo
147: // cacheado es via innerHTML pues esta técnica necesita de hecho serializar el nodo
148: // al serializar el mismo es cuando aprovechamos para expandir la marca del cache.
149: // Por tanto NO podemos cachear un elemento que no puede ser enviado su contenido al cliente
150: // por JavaScript usando innerHTML (aunque si sus hijos pero eso no se decide aquí).
151: // El caso es que no todo nodo cacheado se expandirá cuando se envía como JavaScript,
152: // hay más casos tal y como en la serialización del documento en fast load,
153: // sin embargo a priori no sabemos si nuestro fragmento, por ejemplo, va a ser incluido
154: // via <include> en carga (por serialización del documento entero) o como respuesta a un evento del cliente (por JavaScript).
155: // Tampoco sabemos que navegador usará el template por tanto suponemos MSIE que
156: // es el más restrictivo en los posibles elementos con innerHTML (todos los casos del FireFox
157: // están incluidos en los casos del MSIE pero no viceversa).
158:
159: if (!(elem instanceof HTMLElement))
160: return false; // Caso de svg embebido por ejemplo, sólo tienen innerHTML los HTMLElement
161:
162: // Suponemos que es un MSIE porque MSIE es más restrictivo que FireFox
163: // y si cacheamos debe ser para ambos navegadores válido
164: boolean isHTML = getMarkupTemplateVersion().isHTML();
165: boolean isMSIE = true;
166:
167: JSHTMLElementRenderImpl render = (JSHTMLElementRenderImpl) JSNodeRenderImpl
168: .getJSNodeRender(elem, isMSIE);
169: return !render.isHTMLElementValidWithInnerHTML(
170: (HTMLElement) elem, isHTML);
171: }
172:
173: public static void normalizeContent(HTMLDocument doc) {
174: // Buscamos que el DOM del navegador sea idéntico al del servidor,
175: // los navegadores a veces hacen modificaciones por su cuenta
176:
177: // Firefox añade automáticamente TBODY por eso lo añadimos nosotros
178: // porque sino los paths fallan
179: NodeList tables = doc.getElementsByTagName("table");
180: for (int i = 0; i < tables.getLength(); i++) {
181: HTMLTableElement table = (HTMLTableElement) tables.item(i);
182: boolean hasTBody;
183: if (ItsNatImpl.isOldXerces()) // El getTBodies no funciona bien en el Xerces de la JVM 1.5 (v 2.6.2)
184: hasTBody = (ItsNatTreeWalker
185: .getFirstChildElementWithTag(table, "TBODY") != null);
186: else
187: hasTBody = (table.getTBodies().getLength() == 0);
188:
189: if (!hasTBody) {
190: // No tiene TBODY, añadimos (suponemos que tampoco hay un THEAD etc)
191: HTMLElement tbody = (HTMLElement) doc
192: .createElement("tbody");
193: Node child = table.getFirstChild();
194: while (child != null) {
195: table.removeChild(child);
196: tbody.appendChild(child);
197: child = table.getFirstChild();
198: }
199: table.appendChild(tbody);
200: }
201: }
202:
203: // Eliminamos los nodos de texto entre <tr> y <td> y entre <td> y <td>
204: // es decir los nodos de texto bajo <tr>. Esto es debido a que cuando
205: // se añaden via JavaScript en FireFox existe un bug en el que
206: // indenta visualmente este nodo de texto como si fuera un <td>
207: // (aunque no aparezca en el DOM como td), así ganamos además rendimiento
208: // al no enviar espacios/fines de línea al cliente y por supuesto menos memoria usada.
209: // Los comentarios sí son soportados por FireFox y MSIE los dejamos
210:
211: NodeList trs = doc.getElementsByTagName("tr");
212: for (int i = 0; i < trs.getLength(); i++) {
213: HTMLTableRowElement row = (HTMLTableRowElement) trs.item(i);
214: ItsNatDOMUtilInternal.removeAllChildTextNodes(row);
215: }
216:
217: // En el caso de <ul> y <ol> típicamente serán usados en trees
218: // eliminamos los espacios entre los nodos hijos <li> (son inútiles) para acelerar y diminuir la memoria
219:
220: NodeList uls = doc.getElementsByTagName("ul");
221: for (int i = 0; i < uls.getLength(); i++) {
222: HTMLUListElement ul = (HTMLUListElement) uls.item(i);
223: ItsNatDOMUtilInternal.removeAllChildTextNodes(ul);
224: }
225:
226: NodeList ols = doc.getElementsByTagName("ol");
227: for (int i = 0; i < ols.getLength(); i++) {
228: HTMLOListElement ol = (HTMLOListElement) ols.item(i);
229: ItsNatDOMUtilInternal.removeAllChildTextNodes(ol);
230: }
231: }
232: }
|