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.dom.parse;
015:
016: import org.itsnat.core.domutil.ItsNatDOMUtil;
017: import org.w3c.dom.*;
018: import org.itsnat.impl.core.domutil.ItsNatDOMUtilInternal;
019: import org.w3c.dom.html.HTMLBodyElement;
020: import org.w3c.dom.html.HTMLDocument;
021: import org.w3c.dom.html.HTMLHeadElement;
022: import org.w3c.dom.html.HTMLHtmlElement;
023: import org.xml.sax.InputSource;
024:
025: public class HTMLParserUtil {
026: protected static final ItsNatDOMParser parser = new NekoHTMLParser(); // singleton
027:
028: /**
029: * Creates a new instance of HTMLParserUtil
030: */
031: public HTMLParserUtil() {
032: }
033:
034: public static Document parse(InputSource input, String encoding) {
035: input.setEncoding(encoding);
036: Document doc = parser.parse(input);
037:
038: normalizeHeader((HTMLDocument) doc);
039:
040: return doc;
041: }
042:
043: private static void normalizeHeader(HTMLDocument doc) {
044: /* Adaptamos el documento a que se parezca lo más posible al DOM
045: * que mostrarán los navegadores, además intentamos normalizar
046: * el documento para evitar que los métodos HTMLDocument.normalizeHtmlElement(),
047: * y normalizeBody() de Xerces (en HTMLDocumentImpl) hagan cambios en el árbol por su cuenta cuando
048: * no encuentren el <head> y el <body>, pues además de crearlos hace inserciones inexperadas
049: * que cambiarían el layout del documento sin saberlo el usuario. Así dejamos
050: * el documento preparado para recorrerse "en modo lectura" sin cambios indirectos.
051: */
052:
053: cleanDocumentChildren(doc);
054:
055: HTMLHtmlElement html = normalizeHtmlElement(doc); // crea <html> si no existiera
056:
057: HTMLBodyElement body = normalizeBody(html); // Asegura que hay <body>, si no lo crea
058:
059: HTMLHeadElement head = normalizeHead(html); // crea <head> si no existiera y lo pone al principio de todo (antes del <body> por tanto)
060:
061: /* Tanto Firefox como MSIE no reflejan en el DOM los
062: * nodos bajo <html> que no sean <head> y <body> incluidos
063: * nodos de texto, comentarios e incluso elementos. Aunque
064: * se vean en el visor del código fuente, no están en el DOM.
065: * Por tanto para que exista coherencia entre cliente y servidor
066: * (para el cálculo de paths) los eliminamos.
067: */
068: ItsNatDOMUtilInternal.removeAllChildNotElement(html);
069: }
070:
071: /*
072: public static Document createDocument(String rootName)
073: {
074: DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
075: try
076: {
077: DocumentBuilder builder = factory.newDocumentBuilder();
078: DOMImplementation domImpl = builder.getDOMImplementation();
079: Document doc = domImpl.createDocument(null,rootName,null);
080: //doc.setEncoding("iso-8859-1"); // DOM-3 puede cambiar
081: return doc;
082: }
083: catch(ParserConfigurationException ex)
084: {
085: throw new ItsNatException(ex);
086: }
087: }
088: */
089:
090: protected static void cleanDocumentChildren(Document doc) {
091: /* Eliminamos los nodos bajo el HTMLDocument que no sean
092: * el DocumentType y el <html>, por ejemplo comentarios y nodos de texto,
093: * pues Firefox no los muestra en el DOM (aunque se vea en el código fuente),
094: * pues el parser Xerces elimina los nodos de texto pero no los comentarios.
095: */
096:
097: Node node = doc.getFirstChild();
098: while (node != null) {
099: int type = node.getNodeType();
100: if ((type != Node.ELEMENT_NODE)
101: && (type != Node.DOCUMENT_TYPE_NODE)) {
102: Node next = node.getNextSibling();
103: doc.removeChild(node);
104: node = next;
105: } else {
106: node = node.getNextSibling();
107: }
108: }
109: }
110:
111: protected static HTMLHtmlElement normalizeHtmlElement(
112: HTMLDocument doc) {
113: // Obtenemos el <html> sin usar el Document.normalizeHtmlElement
114: // pues dicho método crea el elemento si no existiera pero además hace inserciones incorrectas
115: Node node = doc.getFirstChild();
116: while (node != null) {
117: if (node instanceof HTMLHtmlElement)
118: return (HTMLHtmlElement) node;
119:
120: node = node.getNextSibling();
121: }
122: // Es muy raro pero bueno
123: HTMLHtmlElement html = (HTMLHtmlElement) doc
124: .createElement("html");
125: doc.appendChild(html);
126: return html;
127: }
128:
129: protected static HTMLBodyElement normalizeBody(HTMLHtmlElement html) {
130: // Asegura que existe el <body>, si no existe lo creamos, pues
131: // el método HTMLDocument.normalizeBody() crea dicho nodo si no existe
132: // y se trata de evitar añadir nodos de forma indirecta
133: Node node = html.getFirstChild();
134: while (node != null) {
135: if (node instanceof HTMLBodyElement)
136: return (HTMLBodyElement) node;
137:
138: node = node.getNextSibling();
139: }
140: // Es el caso ausencia de <body> y de <head> en donde los elementos
141: // están directamente por debajo de <html>
142: Node children = ItsNatDOMUtil.extractChildren(html); // Devuelve un DocumentFragment si hay varios hijos
143: HTMLBodyElement body = (HTMLBodyElement) html
144: .getOwnerDocument().createElement("body");
145: if (children != null)
146: body.appendChild(children);
147: html.appendChild(body);
148: return body;
149: }
150:
151: protected static HTMLHeadElement normalizeHead(HTMLHtmlElement html) {
152: // Obtenemos el <head>, si no existe lo creamos, pues
153: // el método HTMLDocument.normalizeBody() crea dicho nodo si no existe
154: // y se trata de evitar añadir nodos de forma indirecta
155: Node node = html.getFirstChild();
156: while (node != null) {
157: if (node instanceof HTMLHeadElement)
158: return (HTMLHeadElement) node;
159:
160: node = node.getNextSibling();
161: }
162: // Es muy raro pero bueno
163: HTMLHeadElement head = (HTMLHeadElement) html
164: .getOwnerDocument().createElement("head");
165: html.insertBefore(head, html.getFirstChild());
166: return head;
167: }
168: }
|