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