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}