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;
015:
016: import org.itsnat.core.ItsNatException;
017: import org.w3c.dom.Attr;
018: import org.w3c.dom.Document;
019: import org.w3c.dom.Element;
020: import org.w3c.dom.Node;
021: import org.w3c.dom.NodeList;
022: import org.w3c.dom.views.AbstractView;
023:
024: /**
025: *
026: Los paths no considerarán los nodos de texto que son "conflictivos", los filtramos
027: otros nodos si cuentan tal y como los comentarios, DocumentType etc.
028:
029: MSIE filtra los nodos con espacios y finales de línea salvo que
030: los introduzcamos explícitamente via DOM con createTextNode/appendChild etc
031: por eso sólo consideramos los elementos.
032: http://www.w3schools.com/dom/dom_mozilla_vs_ie.asp
033: http://www.w3schools.com/dom/dom_mozilla_vs_ie.asp
034: *
035: Por otra parte así evitamos el problema de que al eliminar un elemento queden los
036: nodos anterior y posterior juntos, este es un problema para el control remoto
037: pues cuando Firefox lee el DOM serializado a observar normaliza el árbol.
038: De todas formas con un par de trucos conseguimos también contruir y resolver paths para
039: nodos de texto.
040: *
041: * @author jmarranz
042: */
043: public class DOMPathResolver {
044:
045: /**
046: * Creates a new instance of DOMPathResolver
047: */
048: public DOMPathResolver() {
049: }
050:
051: private static String[] getArrayPathFromString(String pathStr) {
052: if ((pathStr == null) || pathStr.equals("null"))
053: return null;
054: String[] path = pathStr.split(",");
055: return path;
056: }
057:
058: private static Node getChildNodeFromPos(Node parent, int pos,
059: boolean isTextNode) {
060: if (!parent.hasChildNodes())
061: return null;
062:
063: NodeList childList = parent.getChildNodes();
064: int currPos = 0;
065: for (int i = 0; i < childList.getLength(); i++) {
066: Node node = childList.item(i);
067: int type = node.getNodeType();
068: if (currPos == pos) {
069: if (isTextNode) {
070: if (type == Node.TEXT_NODE)
071: return node;
072: else
073: return null; // No se ha encontrado (nos hemos pasado) por la normalizacion etc
074: } else if (type != Node.TEXT_NODE)
075: return node;
076: // Si es nodo texto lo ignoramos y seguimos iterando
077: }
078:
079: if (type != Node.TEXT_NODE) {
080: currPos++;
081: }
082: }
083: return null; // No encontrado
084: }
085:
086: private static Node getChildNodeFromStrPos(Node parent,
087: String posStr) {
088: // Vemos si se especifica un atributo o nodo de texto
089: int posBracket = posStr.indexOf('[');
090: if (posBracket == -1) {
091: int pos = Integer.parseInt(posStr);
092: return getChildNodeFromPos(parent, pos, false);
093: } else {
094: int pos = Integer.parseInt(posStr.substring(0, posBracket));
095: // Se especifica un atributo: num[@attrName]
096: // o nodo de texto: num[text]
097: if (posStr.charAt(posBracket + 1) == '@') // Atributo
098: {
099: String attrName = posStr.substring(posBracket + 2,
100: posStr.length() - 1);
101: Node child = getChildNodeFromPos(parent, pos, false);
102: return ((Element) child).getAttributeNode(attrName);
103: // Se devuelve un Attr
104: } else {
105: // Nodo de texto
106: return getChildNodeFromPos(parent, pos, true);
107: }
108: }
109: }
110:
111: private static Node getNodeFromArrayPath(String[] arrayPath,
112: Node topParent, Document doc, AbstractView view) {
113: if (arrayPath[0].equals("window"))
114: return (Node) view;
115: else if (arrayPath[0].equals("document"))
116: return (Node) doc;
117:
118: if (topParent == null)
119: topParent = doc;
120:
121: Node node = topParent;
122:
123: for (int i = 0; i < arrayPath.length; i++) {
124: String posStr = arrayPath[i];
125: if (i < arrayPath.length - 1) // No es el último
126: {
127: int pos = Integer.parseInt(posStr);
128: node = getChildNodeFromPos(node, pos, false);
129: } else // es el último
130: {
131: node = getChildNodeFromStrPos(node, posStr);
132: }
133: }
134: return node;
135: }
136:
137: public static Node getNodeFromPath(String pathStr, Node topParent,
138: Document doc, AbstractView view) {
139: String[] path = getArrayPathFromString(pathStr);
140: if (path == null)
141: return null;
142:
143: return getNodeFromArrayPath(path, topParent, doc, view);
144: }
145:
146: private static String getNodeChildPosition(Node node) {
147: Node parent = node.getParentNode();
148: NodeList list = parent.getChildNodes();
149: int len = list.getLength();
150: int pos = 0; // la posición del nodo o la del nodo de texto pero ignorando los nodos de texto anteriores
151: for (int i = 0; i < len; i++) {
152: Node nodeItem = list.item(i);
153: if (nodeItem == node)
154: return Integer.toString(pos); // Así admitimos que node pueda ser un TextNode
155: if (nodeItem.getNodeType() != Node.TEXT_NODE) {
156: pos++; // Ignoramos los nodos de texto excepto el dado como argumento (si es un TextNode)
157: }
158: }
159: return "-1";
160: }
161:
162: private static String getStringPathFromArray(String[] path) {
163: StringBuffer code = new StringBuffer();
164: for (int i = 0; i < path.length; i++) {
165: if (i != 0)
166: code.append(",");
167: code.append(path[i]);
168: }
169: return code.toString();
170: }
171:
172: private static int getNodeDeep(Node node, Node topParent) {
173: // Cuenta cuantos padres tiene hasta el padre dado incluido éste
174: int i = 0;
175: while (node != topParent) {
176: i++;
177: node = node.getParentNode();
178: }
179: return i;
180: }
181:
182: private static String[] getNodePath(Node nodeLeaf, Node topParent) {
183: // Si topParent es null devuelve un path absoluto, es decir hasta el documento como padre
184: if (nodeLeaf == null)
185: return null;
186:
187: if (topParent == null)
188: topParent = nodeLeaf.getOwnerDocument();
189:
190: int type = nodeLeaf.getNodeType();
191: if (type == Node.DOCUMENT_NODE)
192: return new String[] { "document" };
193: else if (type == AbstractViewImpl.ABSTRACT_VIEW)
194: return new String[] { "window" };
195:
196: Node node = nodeLeaf;
197: if (type == Node.ATTRIBUTE_NODE)
198: node = ((Attr) nodeLeaf).getOwnerElement();
199:
200: int len = getNodeDeep(node, topParent);
201: String[] path = new String[len];
202: for (int i = len - 1; i >= 0; i--) {
203: String pos;
204: Node parent = node.getParentNode();
205: if (parent == null)
206: throw new ItsNatException("Unexpected error");
207: else
208: pos = getNodeChildPosition(node);
209:
210: path[i] = pos;
211: node = node.getParentNode();
212: }
213:
214: path[len - 1] += getSuffix(nodeLeaf);
215:
216: return path;
217: }
218:
219: private static String getSuffix(Node nodeLeaf) {
220: int type = nodeLeaf.getNodeType();
221: if (type == Node.TEXT_NODE)
222: return getTextNodeSuffix();
223: else if (type == Node.ATTRIBUTE_NODE)
224: return "[@" + ((Attr) nodeLeaf).getName() + "]"; // La @ sobra pero es para seguir la sintaxis de XPath
225: else
226: return ""; // No tiene sufijo
227: }
228:
229: public static String getTextNodeSuffix() {
230: return "[text]";
231: }
232:
233: public static String getStringPathFromNode(Node node, Node topParent) {
234: if (node == null)
235: return "null";
236:
237: String[] path = getNodePath(node, topParent);
238: return getStringPathFromArray(path);
239: }
240:
241: public static String getRelativeStringPathFromNodeParent(Node child) {
242: // Posición relativa respecto al padre
243: if (child == null)
244: return "null";
245:
246: return getStringPathFromNode(child, child.getParentNode());
247: }
248:
249: public static String removeTextNodeSuffix(String path) {
250: int len = path.length();
251: if (path.charAt(len - 1) != ']')
252: return path; // No tiene sufijo
253: path = path.substring(0, len - getTextNodeSuffix().length());
254: return path;
255: }
256:
257: }
|