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