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}