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