001/* 002 * (C) Copyright 2006-2009 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Thomas Roger 016 */ 017 018package org.nuxeo.ecm.platform.annotations.gwt.client.view.decorator; 019 020import org.nuxeo.ecm.platform.annotations.gwt.client.AnnotationConstant; 021import org.nuxeo.ecm.platform.annotations.gwt.client.controler.AnnotationController; 022import org.nuxeo.ecm.platform.annotations.gwt.client.model.Annotation; 023import org.nuxeo.ecm.platform.annotations.gwt.client.util.Utils; 024import org.nuxeo.ecm.platform.annotations.gwt.client.util.XPathUtil; 025import org.nuxeo.ecm.platform.annotations.gwt.client.view.listener.AnnotationPopupEventListener; 026 027import com.allen_sauer.gwt.log.client.Log; 028import com.google.gwt.dom.client.Document; 029import com.google.gwt.dom.client.Node; 030import com.google.gwt.dom.client.SpanElement; 031import com.google.gwt.dom.client.Text; 032import com.google.gwt.user.client.DOM; 033import com.google.gwt.user.client.Element; 034import com.google.gwt.user.client.Event; 035 036/** 037 * @author <a href="mailto:troger@nuxeo.com">Thomas Roger</a> 038 */ 039public class NuxeoDecoratorVisitor implements DecoratorVisitor { 040 041 protected boolean decorating; 042 043 protected final Node startNode; 044 045 protected final Node endNode; 046 047 protected boolean started; 048 049 protected int startOffset; 050 051 protected int endOffset; 052 053 protected final Annotation annotation; 054 055 protected final AnnotationController controller; 056 057 protected boolean endNodeFound = false; 058 059 protected boolean endNodeBeforeStartNode = false; 060 061 protected Node currentNode; 062 063 public NuxeoDecoratorVisitor(Annotation annotation, AnnotationController controller) { 064 this.annotation = annotation; 065 this.controller = controller; 066 XPathUtil xpathUtil = new XPathUtil(); 067 Document document = Document.get(); 068 startNode = xpathUtil.getNode(annotation.getStartContainer().getXpath(), document).get(0); 069 startOffset = annotation.getStartContainer().getOffset(); 070 endNode = xpathUtil.getNode(annotation.getEndContainer().getXpath(), document).get(0); 071 endOffset = annotation.getEndContainer().getOffset(); 072 Log.debug("Decorator -- start node: " + startNode + ";text: " 073 + ((com.google.gwt.dom.client.Element) startNode).getInnerHTML() + ";parent html: " 074 + ((Element) startNode.getParentNode()).getInnerHTML()); 075 Log.debug("Decorator -- end node: " + endNode + ";text: " 076 + ((com.google.gwt.dom.client.Element) endNode).getInnerHTML() + ";parent html: " 077 + ((Element) endNode.getParentNode()).getInnerHTML()); 078 Log.debug("Decorator -- start offset: " + startOffset + "; end offset: " + endOffset); 079 } 080 081 public void process(Node node) { 082 currentNode = node; 083 checkEndNodeBeforeStartNode(); 084 shouldStartProcess(); 085 processNodeIfStarted(); 086 } 087 088 protected void checkEndNodeBeforeStartNode() { 089 if (started || startNode.equals(endNode)) { 090 return; // start node already found 091 } 092 if (currentNode.equals(endNode)) { 093 Log.debug("Decorator -- EndNodeBeforeStartNode found."); 094 endNodeBeforeStartNode = true; 095 } 096 } 097 098 protected void shouldStartProcess() { 099 if (currentNode.equals(startNode) && !started) { 100 Log.debug("Decorator -- start node found: " + currentNode + ";text: " + currentNode.getNodeValue()); 101 Log.debug("Decorator -- parent html: " + ((Element) currentNode.getParentNode()).getInnerHTML()); 102 started = true; 103 if (startNode.equals(endNode)) { 104 endNodeFound = true; 105 } 106 } 107 } 108 109 protected void processNodeIfStarted() { 110 if (started) { 111 processNode(); 112 } 113 } 114 115 protected void processNode() { 116 if (!decorating) { 117 processToFirstNode(); 118 } else { 119 decorateNode(); 120 } 121 } 122 123 protected void processToFirstNode() { 124 Log.debug("Decorator -- processToFirstNode: " + currentNode.getNodeName()); 125 if (!(currentNode.getNodeType() == Node.TEXT_NODE)) { 126 return; 127 } 128 Text text = (Text) currentNode; 129 String data = text.getData(); 130 Log.debug("Decorator -- text data before: " + data); 131 data = Utils.removeWhitespaces(data, currentNode); 132 Log.debug("Decorator -- text data after: " + data); 133 if (data.length() < startOffset) { 134 startOffset -= data.length(); 135 if (startNode.equals(endNode)) { 136 endOffset -= data.length(); 137 } 138 return; 139 } 140 decorating = true; 141 142 String notInData = data.substring(0, startOffset); 143 decorateText(data.substring(startOffset)); 144 text.setData(notInData); 145 } 146 147 protected void decorateText(String textToDecorate) { 148 checkEndNodeFound(); 149 150 String afterText = getAfterText(); 151 Log.debug("Decorator -- afterText: " + afterText); 152 if (afterText.length() > 0) { 153 textToDecorate = textToDecorate.substring(0, textToDecorate.length() - afterText.length()); 154 } 155 156 if (currentNode.getParentNode().getNodeName().equalsIgnoreCase("tr")) { 157 // don't add nodes to tr 158 return; 159 } 160 161 com.google.gwt.dom.client.Element spanElement = decorateTextWithSpan(textToDecorate); 162 if (spanElement == null) { 163 if (afterText.length() > 0) { 164 Document document = currentNode.getOwnerDocument(); 165 Node parent = currentNode.getParentNode(); 166 insertBefore(parent, currentNode, document.createTextNode(afterText)); 167 } 168 } else { 169 Log.debug("Decorator -- span element: " + spanElement.getInnerHTML()); 170 if (afterText.length() > 0) { 171 Document document = currentNode.getOwnerDocument(); 172 Node parent = currentNode.getParentNode(); 173 insertBefore(parent, spanElement.getNextSibling(), document.createTextNode(afterText)); 174 } 175 } 176 } 177 178 protected void checkEndNodeFound() { 179 Log.debug("Decorator -- endNode: " + endNode); 180 Log.debug("Decorator -- currentNode: " + currentNode); 181 Log.debug("Decorator -- endNode == currentNode?: " + currentNode.equals(endNode)); 182 if (currentNode.equals(endNode)) { 183 endNodeFound = true; 184 Log.debug("Decorator -- end node found: " + currentNode + ";text: " + currentNode.getNodeValue()); 185 Log.debug("Decorator -- parent html: " + ((Element) currentNode.getParentNode()).getInnerHTML()); 186 } 187 } 188 189 protected String getAfterText() { 190 Text text = (Text) currentNode; 191 String data = text.getData(); 192 Log.debug("Decorator -- text data before: " + data); 193 data = Utils.removeWhitespaces(data, currentNode); 194 Log.debug("Decorator -- text data after: " + data); 195 196 String afterText = ""; 197 if (endNodeFound) { 198 if (data.length() > endOffset) { 199 afterText = data.substring(endOffset); 200 data = data.substring(0, endOffset); 201 } 202 endOffset -= data.length(); 203 } 204 return afterText; 205 } 206 207 protected com.google.gwt.dom.client.Element decorateTextWithSpan(String data) { 208 if (data.trim().length() == 0) { 209 // don't add span to empty text 210 return null; 211 } 212 213 Document document = currentNode.getOwnerDocument(); 214 SpanElement spanElement = getSpanElement(document); 215 spanElement.setInnerText(data); 216 Node parent = currentNode.getParentNode(); 217 String className = AnnotationConstant.IGNORED_ELEMENT + " " + controller.getDecorateClassName() + " " 218 + AnnotationConstant.DECORATE_CLASS_NAME + annotation.getId(); 219 if (parent.getNodeName().equalsIgnoreCase("span")) { 220 String parentClassName = ((SpanElement) parent.cast()).getClassName(); 221 if (parentClassName.indexOf(controller.getDecorateClassName()) != -1) { 222 className = parentClassName + " " + AnnotationConstant.DECORATE_CLASS_NAME + annotation.getId(); 223 } 224 } 225 spanElement.setClassName(className); 226 insertBefore(parent, currentNode.getNextSibling(), spanElement); 227 return spanElement; 228 } 229 230 protected void decorateNode() { 231 if (endNodeBeforeStartNode 232 && (endNode.equals(currentNode.getPreviousSibling()) || endNode.equals(currentNode.getParentNode()))) { 233 endNodeFound = true; 234 endOffset = 0; 235 return; 236 } 237 if (!(currentNode.getNodeType() == Node.TEXT_NODE)) { 238 if (endNode.equals(currentNode.getPreviousSibling())) { 239 endNodeFound = true; 240 endOffset = 0; 241 } else if (endNode.equals(currentNode)) { 242 endNodeFound = true; 243 } 244 return; 245 } 246 Text text = (Text) currentNode; 247 String data = text.getData(); 248 data = Utils.removeWhitespaces(data, currentNode); 249 decorateText(data); 250 currentNode.getParentNode().removeChild(currentNode); 251 } 252 253 protected SpanElement getSpanElement(Document document) { 254 SpanElement spanElement = document.createSpanElement(); 255 DOM.sinkEvents((Element) spanElement.cast(), Event.ONMOUSEOVER | Event.ONMOUSEOUT); 256 DOM.setEventListener((Element) spanElement.cast(), 257 AnnotationPopupEventListener.getAnnotationPopupEventListener(annotation, controller)); 258 return spanElement; 259 } 260 261 protected void insertBefore(Node parent, Node child, Node newChild) { 262 if (child == null) { 263 parent.appendChild(newChild); 264 } else { 265 parent.insertBefore(newChild, child); 266 } 267 } 268 269 public boolean doBreak() { 270 return endNodeFound && endOffset <= 0; 271 } 272 273}