001/* 002 * (C) Copyright 2006-2008 Nuxeo SAS (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 * Alexandre Russel 016 * 017 * $Id$ 018 */ 019 020package org.nuxeo.ecm.platform.annotations.gwt.client.view.decorator; 021 022import java.util.ArrayList; 023import java.util.List; 024 025import org.nuxeo.ecm.platform.annotations.gwt.client.AnnotationConstant; 026import org.nuxeo.ecm.platform.annotations.gwt.client.controler.AnnotationController; 027import org.nuxeo.ecm.platform.annotations.gwt.client.model.Annotation; 028import org.nuxeo.ecm.platform.annotations.gwt.client.view.listener.AnnotationPopupEventListener; 029 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 Alexandre Russel 040 */ 041public class AnnoteaDecoratorVisitor implements DecoratorVisitor { 042 private boolean decorating; 043 044 private boolean previousIsCarriageReturnElement; 045 046 private static List<String> carriagesReturnedElements = new ArrayList<String>(); 047 static { 048 carriagesReturnedElements.add("div"); 049 carriagesReturnedElements.add("br"); 050 carriagesReturnedElements.add("p"); 051 } 052 053 private boolean lastCharIsSpace; 054 055 private final Node startNode; 056 057 private boolean started; 058 059 private int offset; 060 061 private int textToAnnotate; 062 063 private final Annotation annotation; 064 065 private final AnnotationController controller; 066 067 public AnnoteaDecoratorVisitor(Node startNode, int annotatedText, int offset, Annotation annotation, 068 AnnotationController controller) { 069 this.startNode = startNode; 070 this.textToAnnotate = annotatedText; 071 this.offset = offset; 072 this.annotation = annotation; 073 this.controller = controller; 074 } 075 076 public boolean isLastCharIsSpace() { 077 return lastCharIsSpace; 078 } 079 080 public void setLastCharIsSpace(boolean lastCharIsSpace) { 081 this.lastCharIsSpace = lastCharIsSpace; 082 } 083 084 public boolean doBreak() { 085 return textToAnnotate == 0; 086 } 087 088 public void process(Node node) { 089 if (node.equals(startNode)) { 090 started = true; 091 } else if (started) { 092 if (!decorating) { 093 processToFirstNode(node); 094 } else { 095 processNode(node); 096 } 097 if (carriagesReturnedElements.contains(node.getNodeName().toLowerCase())) { 098 previousIsCarriageReturnElement = true; 099 } else { 100 previousIsCarriageReturnElement = false; 101 } 102 } 103 } 104 105 private void insertBefore(Node parent, Node child, Node newChild) { 106 if (child == null) { 107 parent.appendChild(newChild); 108 } else { 109 parent.insertBefore(newChild, child); 110 } 111 } 112 113 private void processNode(Node node) { 114 if (!(node.getNodeType() == Node.TEXT_NODE)) { 115 if (node.getNodeName().equalsIgnoreCase("td")) { 116 textToAnnotate -= 1; 117 } 118 return; 119 } 120 Text text = (Text) node; 121 Node parent = text.getParentNode(); 122 String data = text.getData(); 123 processDecoratedNode(node, data, parent); 124 node.getParentNode().removeChild(node); 125 } 126 127 public String[] getSelectedText(String rawText, int length) { 128 String text = ""; 129 for (int x = 0; x <= rawText.length(); x++) { 130 text = rawText.substring(0, x); 131 text = removeWhiteSpace(text); 132 if (text.length() == length) { 133 return new String[] { text, rawText.substring(0, x), rawText.substring(x) }; 134 } 135 } 136 return new String[] { text, rawText, "" }; 137 } 138 139 public String removeWhiteSpace(String data) { 140 data = data.replaceAll("\\s+", " "); 141 boolean startWithSpace = data.startsWith(" "); 142 boolean endWithSpace = data.endsWith(" "); 143 data = data.trim(); 144 if (lastCharIsSpace && !startWithSpace && !previousIsCarriageReturnElement) { 145 data = " " + data; 146 } else if (!lastCharIsSpace && startWithSpace && !previousIsCarriageReturnElement) { 147 data = " " + data; 148 } 149 lastCharIsSpace = endWithSpace; 150 return data; 151 } 152 153 private SpanElement getSpanElement(Document document) { 154 SpanElement spanElement = document.createSpanElement(); 155 DOM.sinkEvents((Element) spanElement.cast(), Event.ONMOUSEOVER | Event.ONMOUSEOUT); 156 DOM.setEventListener((Element) spanElement.cast(), 157 AnnotationPopupEventListener.getAnnotationPopupEventListener(annotation, controller)); 158 spanElement.setClassName(AnnotationConstant.IGNORED_ELEMENT + " " + controller.getDecorateClassName() + " " 159 + AnnotationConstant.DECORATE_CLASS_NAME + annotation.getId()); 160 return spanElement; 161 } 162 163 private void processToFirstNode(Node node) { 164 if (!(node.getNodeType() == Node.TEXT_NODE)) { 165 return; 166 } 167 Text text = (Text) node; 168 String data = text.getData(); 169 if (data.length() < offset) { 170 offset -= data.length(); 171 return; 172 } 173 decorating = true; 174 Node parent = text.getParentNode(); 175 if (data.endsWith(" ")) { 176 lastCharIsSpace = true; 177 } 178 String notInData = data.substring(0, offset); 179 text.setData(notInData); 180 processDecoratedNode(node, data.substring(offset), parent); 181 } 182 183 private void processDecoratedNode(Node node, String data, Node parent) { 184 String[] selectedText = getSelectedText(data, textToAnnotate); 185 if (selectedText[1].trim().length() == 0 && selectedText[2].trim().length() == 0 186 && node.getParentNode().getNodeName().equalsIgnoreCase("tr")) { 187 // don't add nodes to tr 188 textToAnnotate -= selectedText[0].length(); 189 return; 190 } 191 Document document = node.getOwnerDocument(); 192 SpanElement spanElement = getSpanElement(document); 193 spanElement.setInnerText(selectedText[1]); 194 insertBefore(parent, node.getNextSibling(), spanElement); 195 if (selectedText[2].length() > 0) { 196 insertBefore(parent, spanElement.getNextSibling(), document.createTextNode(selectedText[2])); 197 } 198 textToAnnotate -= selectedText[0].length(); 199 } 200}