/******************************************************************************* * Copyright (c) 2006 Sybase, Inc. and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Sybase, Inc. - initial API and implementation *******************************************************************************/ package org.eclipse.jst.pagedesigner.dom; import org.eclipse.jst.jsf.common.ui.internal.logging.Logger; import org.eclipse.jst.pagedesigner.IHTMLConstants; import org.eclipse.jst.pagedesigner.PDPlugin; import org.eclipse.jst.pagedesigner.utils.HTMLUtil; import org.eclipse.jst.pagedesigner.validation.caret.IMovementMediator; import org.eclipse.jst.pagedesigner.validation.caret.Target; import org.w3c.dom.Node; import org.w3c.dom.Text; import org.w3c.dom.traversal.NodeIterator; /** * @author mengbo */ public class CaretMoveIterator { private final static Logger _log = PDPlugin .getLogger(CaretMoveIterator.class); private final boolean INNER_DEBUG = false; private NodeIterator _nodeIterator; private IMovementMediator _validator; private IDOMPosition _currentPosition; private boolean _forward; /** * @param nodeIterator * @param validator * @param position * @param forward */ public CaretMoveIterator(NodeIterator nodeIterator, IMovementMediator validator, IDOMPosition position, boolean forward) { super(); _nodeIterator = nodeIterator; _validator = validator; _currentPosition = position; _forward = forward; } /** * @return the node iterator */ public NodeIterator getNodeIterator() { return _nodeIterator; } /** * @return Returns the _currentPosition. */ public IDOMPosition getCurrentPosition() { return _currentPosition; } /** * @param position * The _currentPosition to set. */ public void setCurrentPosition(IDOMPosition position) { _currentPosition = position; } // assume the currentPosition is invalid private IDOMPosition moveOut(Node container) { IDOMPosition result = new DOMRefPosition(container, _forward); String name = container.getNodeName(); if (name != null && EditModelQuery.HTML_STYLE_NODES.contains(name.toLowerCase())) { result = moveToNextPosition(result, _validator); } return result; } /** * @param node * @return the dom position */ public IDOMPosition moveIn(Node node) { IDOMPosition result = null; if (INNER_DEBUG) { _log.info("- Move into: " + node.getLocalName()); //$NON-NLS-1$ } if (_validator.isEditable(new Target(node))) { int index; // Transparent text is not editable, so this is not transparent. if (EditModelQuery.isText(node)) { index = (_forward) ? 0 : ((Text) node).getData().length(); result = new DOMPosition(node, index); // move ahead one pos. IDOMPosition pos = getNextTextPosition(result); if (pos != null) { result = pos; } } else { if (node.hasChildNodes()) { index = _forward ? 0 : node.getChildNodes().getLength(); result = new DOMPosition(node, index); // DOMRefPosition(next, // !_forward); } else { result = new DOMPosition(node, 0); } } } else { if (node.hasChildNodes()) { Node child = _forward ? node.getFirstChild() : node .getLastChild(); result = new DOMRefPosition(child, _forward); while (child != null) { if (_validator.allowsMoveIn(new Target(child))) { result = moveIn(child); break; } child = _forward ? child.getNextSibling() : child .getPreviousSibling(); } } else { // Should be impposible to reach here. result = new DOMPosition(node, 0); } } return result; } private IDOMPosition getNextTextPosition(IDOMPosition position) { IDOMPosition result = null; String value = position.getContainerNode().getNodeValue(); int i = position.getOffset(); if (_forward) { if (i < value.length()) { if (HTMLUtil.isHTMLWhitespace(value.charAt(i))) { while (i < value.length() && HTMLUtil.isHTMLWhitespace(value.charAt(i))) { i++; } result = new DOMPosition(position.getContainerNode(), i); } else if (i < value.length()) { result = new DOMPosition(position.getContainerNode(), i + 1); } } } else { if (i > 0) { if (HTMLUtil.isHTMLWhitespace(value.charAt(i - 1))) { while (i > 0 && HTMLUtil.isHTMLWhitespace(value.charAt(i - 1))) { i--; } result = new DOMPosition(position.getContainerNode(), i); } else if (i > 0) { result = new DOMPosition(position.getContainerNode(), i - 1); } } } return result; } /** * Assume the original position are valid. * * @param position * @param validator * @param _forward * @param referenceImediatly * @return */ private IDOMPosition moveToNextPosition(IDOMPosition position, IMovementMediator validator) { IDOMPosition currentPosition = null; if (validator.isValidPosition(position) && position.isText()) { currentPosition = getNextTextPosition(position); } if (currentPosition == null) { Node nextNode = EditModelQuery.getInstance().getSibling(position, _forward); while (EditModelQuery.isText(nextNode) && ((Text) nextNode).getData().length() == 0) { nextNode = EditModelQuery.getInstance().getSibling(nextNode, _forward); } if (nextNode != null) { // move in? if (validator.allowsMoveIn(new Target(nextNode))) { currentPosition = moveIn(nextNode); // Stop when it is in table. For others we continue search // for text. if (!canStopHere(nextNode) && // EditModelQuery.getInstance().getSibling( currentPosition, _forward) != null) { currentPosition = moveToNextPosition(currentPosition, validator); } } // not allowed to move in. e.g. it's empty string. else { currentPosition = new DOMRefPosition(nextNode, _forward);// skip(position); } } else { if (validator.allowsMoveOut(new Target( getNaviContainer(position)))) { currentPosition = moveOut(getNaviContainer(position)); } } } currentPosition = EditHelper.ensureDOMPosition(currentPosition); if (currentPosition != null && !validator.isValidPosition(currentPosition)) { currentPosition = moveToNextPosition(currentPosition, validator); } return currentPosition; } /** * When the tag starts from new line, or in table, then caret can be put at * 0 offset. * * @param node * @return */ private boolean canStopHere(Node node) { boolean result = false; if (EditModelQuery.isText(node)) { result = true; } else if (node != null && node.getNodeName() != null) { result |= node.getNodeName().equals(IHTMLConstants.TAG_TD); result |= EditModelQuery.isBlockNode(node); } return result; } /** * Move operation position to next edit position. We may need rule to valid * it based on operation ID and direction. We need to pack transparent * string. * * @param currentPosition * @param forward * @param validator * @return the dom position */ public IDOMPosition moveToNextEditPosition(IDOMPosition currentPosition, boolean forward, IMovementMediator validator) { IDOMPosition result = null; if ((currentPosition = moveToNextPosition(currentPosition, validator)) != null) { result = currentPosition; } else { result = _currentPosition; } return result; } private Node getNaviContainer(IDOMPosition position) { if (position.isText()) { return position.getContainerNode().getParentNode(); } return position.getContainerNode(); } }