/* * (C) Copyright 2006-2009 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Contributors: * Thomas Roger */ package org.nuxeo.ecm.platform.annotations.gwt.client.view.decorator; import org.nuxeo.ecm.platform.annotations.gwt.client.AnnotationConstant; import org.nuxeo.ecm.platform.annotations.gwt.client.controler.AnnotationController; import org.nuxeo.ecm.platform.annotations.gwt.client.model.Annotation; import org.nuxeo.ecm.platform.annotations.gwt.client.util.Utils; import org.nuxeo.ecm.platform.annotations.gwt.client.util.XPathUtil; import org.nuxeo.ecm.platform.annotations.gwt.client.view.listener.AnnotationPopupEventListener; import com.allen_sauer.gwt.log.client.Log; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Node; import com.google.gwt.dom.client.SpanElement; import com.google.gwt.dom.client.Text; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; /** * @author <a href="mailto:troger@nuxeo.com">Thomas Roger</a> */ public class NuxeoDecoratorVisitor implements DecoratorVisitor { protected boolean decorating; protected final Node startNode; protected final Node endNode; protected boolean started; protected int startOffset; protected int endOffset; protected final Annotation annotation; protected final AnnotationController controller; protected boolean endNodeFound = false; protected boolean endNodeBeforeStartNode = false; protected Node currentNode; public NuxeoDecoratorVisitor(Annotation annotation, AnnotationController controller) { this.annotation = annotation; this.controller = controller; XPathUtil xpathUtil = new XPathUtil(); Document document = Document.get(); startNode = xpathUtil.getNode(annotation.getStartContainer().getXpath(), document).get(0); startOffset = annotation.getStartContainer().getOffset(); endNode = xpathUtil.getNode(annotation.getEndContainer().getXpath(), document).get(0); endOffset = annotation.getEndContainer().getOffset(); Log.debug("Decorator -- start node: " + startNode + ";text: " + ((com.google.gwt.dom.client.Element) startNode).getInnerHTML() + ";parent html: " + ((Element) startNode.getParentNode()).getInnerHTML()); Log.debug("Decorator -- end node: " + endNode + ";text: " + ((com.google.gwt.dom.client.Element) endNode).getInnerHTML() + ";parent html: " + ((Element) endNode.getParentNode()).getInnerHTML()); Log.debug("Decorator -- start offset: " + startOffset + "; end offset: " + endOffset); } public void process(Node node) { currentNode = node; checkEndNodeBeforeStartNode(); shouldStartProcess(); processNodeIfStarted(); } protected void checkEndNodeBeforeStartNode() { if (started || startNode.equals(endNode)) { return; // start node already found } if (currentNode.equals(endNode)) { Log.debug("Decorator -- EndNodeBeforeStartNode found."); endNodeBeforeStartNode = true; } } protected void shouldStartProcess() { if (currentNode.equals(startNode) && !started) { Log.debug("Decorator -- start node found: " + currentNode + ";text: " + currentNode.getNodeValue()); Log.debug("Decorator -- parent html: " + ((Element) currentNode.getParentNode()).getInnerHTML()); started = true; if (startNode.equals(endNode)) { endNodeFound = true; } } } protected void processNodeIfStarted() { if (started) { processNode(); } } protected void processNode() { if (!decorating) { processToFirstNode(); } else { decorateNode(); } } protected void processToFirstNode() { Log.debug("Decorator -- processToFirstNode: " + currentNode.getNodeName()); if (!(currentNode.getNodeType() == Node.TEXT_NODE)) { return; } Text text = (Text) currentNode; String data = text.getData(); Log.debug("Decorator -- text data before: " + data); data = Utils.removeWhitespaces(data, currentNode); Log.debug("Decorator -- text data after: " + data); if (data.length() < startOffset) { startOffset -= data.length(); if (startNode.equals(endNode)) { endOffset -= data.length(); } return; } decorating = true; String notInData = data.substring(0, startOffset); decorateText(data.substring(startOffset)); text.setData(notInData); } protected void decorateText(String textToDecorate) { checkEndNodeFound(); String afterText = getAfterText(); Log.debug("Decorator -- afterText: " + afterText); if (afterText.length() > 0) { textToDecorate = textToDecorate.substring(0, textToDecorate.length() - afterText.length()); } if (currentNode.getParentNode().getNodeName().equalsIgnoreCase("tr")) { // don't add nodes to tr return; } com.google.gwt.dom.client.Element spanElement = decorateTextWithSpan(textToDecorate); if (spanElement == null) { if (afterText.length() > 0) { Document document = currentNode.getOwnerDocument(); Node parent = currentNode.getParentNode(); insertBefore(parent, currentNode, document.createTextNode(afterText)); } } else { Log.debug("Decorator -- span element: " + spanElement.getInnerHTML()); if (afterText.length() > 0) { Document document = currentNode.getOwnerDocument(); Node parent = currentNode.getParentNode(); insertBefore(parent, spanElement.getNextSibling(), document.createTextNode(afterText)); } } } protected void checkEndNodeFound() { Log.debug("Decorator -- endNode: " + endNode); Log.debug("Decorator -- currentNode: " + currentNode); Log.debug("Decorator -- endNode == currentNode?: " + currentNode.equals(endNode)); if (currentNode.equals(endNode)) { endNodeFound = true; Log.debug("Decorator -- end node found: " + currentNode + ";text: " + currentNode.getNodeValue()); Log.debug("Decorator -- parent html: " + ((Element) currentNode.getParentNode()).getInnerHTML()); } } protected String getAfterText() { Text text = (Text) currentNode; String data = text.getData(); Log.debug("Decorator -- text data before: " + data); data = Utils.removeWhitespaces(data, currentNode); Log.debug("Decorator -- text data after: " + data); String afterText = ""; if (endNodeFound) { if (data.length() > endOffset) { afterText = data.substring(endOffset); data = data.substring(0, endOffset); } endOffset -= data.length(); } return afterText; } protected com.google.gwt.dom.client.Element decorateTextWithSpan(String data) { if (data.trim().length() == 0) { // don't add span to empty text return null; } Document document = currentNode.getOwnerDocument(); SpanElement spanElement = getSpanElement(document); spanElement.setInnerText(data); Node parent = currentNode.getParentNode(); String className = AnnotationConstant.IGNORED_ELEMENT + " " + controller.getDecorateClassName() + " " + AnnotationConstant.DECORATE_CLASS_NAME + annotation.getId(); if (parent.getNodeName().equalsIgnoreCase("span")) { String parentClassName = ((SpanElement) parent.cast()).getClassName(); if (parentClassName.indexOf(controller.getDecorateClassName()) != -1) { className = parentClassName + " " + AnnotationConstant.DECORATE_CLASS_NAME + annotation.getId(); } } spanElement.setClassName(className); insertBefore(parent, currentNode.getNextSibling(), spanElement); return spanElement; } protected void decorateNode() { if (endNodeBeforeStartNode && (endNode.equals(currentNode.getPreviousSibling()) || endNode.equals(currentNode.getParentNode()))) { endNodeFound = true; endOffset = 0; return; } if (!(currentNode.getNodeType() == Node.TEXT_NODE)) { if (endNode.equals(currentNode.getPreviousSibling())) { endNodeFound = true; endOffset = 0; } else if (endNode.equals(currentNode)) { endNodeFound = true; } return; } Text text = (Text) currentNode; String data = text.getData(); data = Utils.removeWhitespaces(data, currentNode); decorateText(data); currentNode.getParentNode().removeChild(currentNode); } protected SpanElement getSpanElement(Document document) { SpanElement spanElement = document.createSpanElement(); DOM.sinkEvents((Element) spanElement.cast(), Event.ONMOUSEOVER | Event.ONMOUSEOUT); DOM.setEventListener((Element) spanElement.cast(), AnnotationPopupEventListener.getAnnotationPopupEventListener(annotation, controller)); return spanElement; } protected void insertBefore(Node parent, Node child, Node newChild) { if (child == null) { parent.appendChild(newChild); } else { parent.insertBefore(newChild, child); } } public boolean doBreak() { return endNodeFound && endOffset <= 0; } }