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