| /******************************************************************************* |
| * 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.commands.range; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.eclipse.jst.pagedesigner.IHTMLConstants; |
| import org.eclipse.jst.pagedesigner.commands.CommandResources; |
| import org.eclipse.jst.pagedesigner.dom.DOMRange; |
| import org.eclipse.jst.pagedesigner.dom.DOMRefPosition; |
| import org.eclipse.jst.pagedesigner.dom.DOMUtil; |
| import org.eclipse.jst.pagedesigner.dom.EditModelQuery; |
| import org.eclipse.jst.pagedesigner.dom.IDOMPosition; |
| import org.eclipse.jst.pagedesigner.viewer.IHTMLGraphicalViewer; |
| import org.eclipse.wst.xml.core.internal.provisional.document.IDOMText; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.Text; |
| |
| /** |
| * @author mengbo |
| */ |
| public class ApplyStyleCommand extends RangeModeCommand { |
| private String _tag; |
| |
| private String _cssProperty; |
| |
| private String _cssPropertyValue; |
| |
| protected Element _applyingNode; |
| |
| /** |
| * @param label |
| * @param viewer |
| */ |
| public ApplyStyleCommand(IHTMLGraphicalViewer viewer, String tag, |
| String property, String value) { |
| super( |
| CommandResources |
| .getString("ApplyStyleCommand.Label.ApplyStyle"), viewer); //$NON-NLS-1$ |
| this._tag = tag; |
| this._cssProperty = property; |
| this._cssPropertyValue = value; |
| } |
| |
| public ApplyStyleCommand(IHTMLGraphicalViewer viewer, Element node, |
| String property, String value) { |
| super( |
| CommandResources |
| .getString("ApplyStyleCommand.Label.ApplyStyle"), viewer); //$NON-NLS-1$ |
| this._applyingNode = node; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jst.pagedesigner.commands.DesignerCommand#doExecute() |
| */ |
| protected DOMRange doRangeExecute(DOMRange range) { |
| if (range == null || range.isEmpty()) { |
| return null; |
| } |
| |
| boolean ordered = range.isOrdered(); |
| IDOMPosition start = ordered ? range.getStartPosition() : range |
| .getEndPosition(); |
| IDOMPosition end = ordered ? range.getEndPosition() : range |
| .getStartPosition(); |
| |
| Node startContainer = start.getContainerNode(); |
| Node endContainer = end.getContainerNode(); |
| |
| Node common = DOMUtil.findCommonAncester(start.getContainerNode(), end |
| .getContainerNode()); |
| if (common == null) { |
| // should not happen. |
| return null; |
| } |
| |
| if (common instanceof Text) { |
| // under the same Text scope |
| range = doTextNodeStyleApply((Text) common, start.getOffset(), end |
| .getOffset()); |
| |
| return range; |
| } else { |
| |
| if (startContainer instanceof Text) { |
| // if the start offset is 0,then skip split the Text |
| if (start.getOffset() > 0) { |
| startContainer = ((Text) startContainer).splitText(start |
| .getOffset()); |
| start = new DOMRefPosition(startContainer, false); |
| } |
| } else { |
| startContainer = start.getNextSiblingNode(); |
| } |
| if (endContainer instanceof Text) { |
| if (end.getOffset() > 0) { |
| endContainer = ((Text) endContainer).splitText(end |
| .getOffset()); |
| endContainer = endContainer.getPreviousSibling(); |
| } else { |
| endContainer = endContainer.getPreviousSibling(); |
| } |
| } else { |
| endContainer = end.getPreviousSiblingNode(); |
| } |
| |
| for (Node node = startContainer; node != endContainer; node = EditModelQuery |
| .getInstance().getNextLeafNeighbor(node)) { |
| if (EditModelQuery.hasAncestor(node, getTag(), true)) { |
| continue; |
| } |
| Element newnode = createStyleElement(); |
| node.getParentNode().insertBefore(newnode, node); |
| newnode.appendChild(node); |
| } |
| if (!EditModelQuery.hasAncestor(endContainer, getTag(), true)) { |
| Element newnode = createStyleElement(); |
| endContainer.getParentNode() |
| .insertBefore(newnode, endContainer); |
| newnode.appendChild(endContainer); |
| } |
| |
| } |
| |
| // merge the style tags |
| |
| for (Node node = startContainer; node != endContainer; node = EditModelQuery |
| .getInstance().getNextLeafNeighbor(node)) { |
| Node stylenode = node; |
| while (stylenode != null |
| && !stylenode.getNodeName().equalsIgnoreCase(getTag())) { |
| stylenode = stylenode.getParentNode(); |
| } |
| if (stylenode == null) { |
| continue; |
| } |
| if (stylenode.getNextSibling() != null |
| && stylenode.getNextSibling().getNodeName() |
| .equalsIgnoreCase(getTag())) { |
| Node sibling = stylenode.getNextSibling(); |
| while (sibling.getFirstChild() != null) { |
| stylenode.appendChild(sibling.getFirstChild()); |
| } |
| stylenode.getParentNode().removeChild(sibling); |
| node = startContainer; |
| } |
| } |
| |
| return new DOMRange(start, end); |
| |
| /* |
| * boolean ordered = range.isOrdered(); IDOMPosition start = ordered ? |
| * range.getStartPosition() : range.getEndPosition(); IDOMPosition end = |
| * ordered ? range.getEndPosition() : range.getStartPosition(); |
| * |
| * Node common = DOMUtil.findCommonAncester(start.getContainerNode(), |
| * end.getContainerNode()); if (common == null) { // should not happen. |
| * return null; } |
| * |
| * DOMRange result = null; if (common instanceof Text) { result = |
| * doTextNodeStyleApply((Text) common, start.getOffset(), |
| * end.getOffset()); } else { IDOMPosition startPosition = start; |
| * IDOMPosition endPosition = end; Node ancester = common; DOMRange[] |
| * leftRange = new DOMRange[1]; DOMRange[] rightRange = new DOMRange[1]; |
| * |
| * startPosition = partialApply(startPosition, ancester, true, |
| * leftRange); endPosition = partialApply(endPosition, ancester, false, |
| * rightRange); DOMRange middle = middleApply(ancester, startPosition, |
| * endPosition); |
| * |
| * IDOMPosition startref = null; if (leftRange[0] != null && |
| * leftRange[0].getStartPosition() != null) { startref = |
| * leftRange[0].getStartPosition(); } else if (middle != null && |
| * middle.getStartPosition() != null) { startref = |
| * middle.getStartPosition(); } else if (rightRange[0] != null && |
| * rightRange[0].getStartPosition() != null) { startref = |
| * rightRange[0].getStartPosition(); } |
| * |
| * IDOMPosition endref = null; if (rightRange[0] != null && |
| * rightRange[0].getEndPosition() != null) { endref = |
| * rightRange[0].getEndPosition(); } else if (middle != null && |
| * middle.getEndPosition() != null) { endref = middle.getEndPosition(); } |
| * else if (leftRange[0] != null && leftRange[0].getEndPosition() != |
| * null) { endref = leftRange[0].getEndPosition(); } |
| * |
| * if (startref == null) { result = null; } else { startref = new |
| * DOMPosition(EditModelQuery.getInstance().getNextLeafNeighbor(startref.getContainerNode()), |
| * 0); System.out.println(startref.toString()); endref = new |
| * DOMPosition(endref.getContainerNode(), 0); result = new |
| * DOMRange(startref, endref); } } |
| * |
| * if (result == null) { return null; } |
| * |
| * if (ordered) { return result; } else { return new |
| * DOMRange(result.getEndPosition(), result.getStartPosition()); } |
| */ |
| } |
| |
| private DOMRange middleApply(Node ancester, IDOMPosition startPosition, |
| IDOMPosition endPosition) { |
| startPosition = skip(startPosition, true); |
| if (startPosition.getNextSiblingNode() == null |
| || startPosition.getOffset() >= endPosition.getOffset()) { |
| return null; |
| } else { |
| List needMove = new ArrayList(); |
| Node startNext = startPosition.getNextSiblingNode(); |
| Node endNext = endPosition.getNextSiblingNode(); |
| while (startNext != null && startNext != endNext) { |
| needMove.add(startNext); |
| startNext = startNext.getNextSibling(); |
| } |
| Element newEle = createStyleElement(); |
| ancester.insertBefore(newEle, startPosition.getNextSiblingNode()); |
| for (int i = 0, n = needMove.size(); i < n; i++) { |
| newEle.appendChild((Node) needMove.get(i)); |
| } |
| return new DOMRange(new DOMRefPosition(newEle, false), |
| new DOMRefPosition(newEle, true)); |
| } |
| |
| } |
| |
| private IDOMPosition partialApply(IDOMPosition position, Node ancester, |
| boolean forward, DOMRange[] result) { |
| IDOMPosition startRef = null, endRef = null; |
| |
| while (position != null && position.getContainerNode() != ancester) { |
| Node container = position.getContainerNode(); |
| if (container instanceof Text) { |
| // splitText will move the position up one level |
| position = splitText(position); |
| } else { |
| // skip those nodes that can't have the style applied. |
| position = skip(position, forward); |
| Node sibling = position.getSibling(forward); |
| if (sibling != null) { |
| List needMove = new ArrayList(); |
| while (sibling != null) { |
| needMove.add(sibling); |
| sibling = forward ? sibling.getNextSibling() : sibling |
| .getPreviousSibling(); |
| } |
| |
| // ok, there is nodes that need the style |
| Element newEle = createStyleElement(); |
| container.insertBefore(newEle, position |
| .getNextSiblingNode()); |
| for (int i = 0, size = needMove.size(); i < size; i++) { |
| newEle.appendChild((Node) needMove.get(i)); |
| } |
| if (startRef == null) { |
| startRef = new DOMRefPosition(newEle, !forward); |
| } |
| endRef = new DOMRefPosition(newEle, forward); |
| } |
| // move the position up one level |
| position = new DOMRefPosition(container, forward); |
| } |
| } |
| if (startRef == null) { |
| result[0] = null; |
| } else { |
| result[0] = forward ? new DOMRange(startRef, endRef) |
| : new DOMRange(endRef, startRef); |
| } |
| return position; |
| } |
| |
| /** |
| * @param position |
| * @return |
| */ |
| private IDOMPosition splitText(IDOMPosition position) { |
| Text text = (Text) position.getContainerNode(); |
| int offset = position.getOffset(); |
| if (offset <= 0) { |
| return new DOMRefPosition(text, false); |
| } else if (offset >= text.getData().length()) { |
| return new DOMRefPosition(text, true); |
| } else { |
| text.splitText(offset); |
| return new DOMRefPosition(text, true); |
| } |
| } |
| |
| /** |
| * @param start |
| * @param end |
| * @param common |
| */ |
| private DOMRange doTextNodeStyleApply(Text textNode, int startOffset, |
| int endOffset) { |
| String data = textNode.getData(); |
| String before = data.substring(0, startOffset); |
| String middle = data.substring(startOffset, endOffset); |
| String tail = data.substring(endOffset); |
| |
| Text middleText = getModel().getDocument().createTextNode(middle); |
| |
| // case 1: normal one |
| if (!isEmptyString(before) && !isEmptyString(tail)) { |
| Node parent = textNode.getParentNode(); |
| parent.insertBefore( |
| getModel().getDocument().createTextNode(before), textNode); |
| Element bnode = createStyleElement(); |
| bnode.appendChild(middleText); |
| parent.insertBefore(bnode, textNode); |
| textNode.setNodeValue(tail); |
| } |
| |
| if (isEmptyString(before) && !isEmptyString(tail)) { |
| Node sibling = textNode.getPreviousSibling(); |
| if (sibling != null |
| && sibling.getNodeName().equalsIgnoreCase(getTag())) { |
| sibling.appendChild(middleText); |
| } else { |
| Node parent = textNode.getParentNode(); |
| parent.insertBefore(getModel().getDocument().createTextNode( |
| before), textNode); |
| Element bnode = createStyleElement(); |
| bnode.appendChild(middleText); |
| parent.insertBefore(bnode, textNode); |
| } |
| textNode.setNodeValue(tail); |
| } |
| |
| if (!isEmptyString(before) && isEmptyString(tail)) { |
| Node sibling = textNode.getNextSibling(); |
| textNode.setNodeValue(before); |
| if (sibling != null |
| && sibling.getNodeName().equalsIgnoreCase(getTag())) { |
| sibling.insertBefore(middleText, sibling.getFirstChild()); |
| } else { |
| Element bnode = createStyleElement(); |
| bnode.appendChild(middleText); |
| textNode.getParentNode().insertBefore(bnode, sibling); |
| } |
| } |
| |
| if (isEmptyString(before) && isEmptyString(tail)) { |
| |
| Node previousSibling = textNode.getPreviousSibling(); |
| Node nextSibling = textNode.getNextSibling(); |
| // |
| if (getTag().equalsIgnoreCase(IHTMLConstants.TAG_P)) { |
| Element bnode = createStyleElement(); |
| bnode.appendChild(middleText); |
| textNode.getParentNode().insertBefore(bnode, textNode); |
| textNode.getParentNode().removeChild(textNode); |
| } |
| // |
| else { |
| if (previousSibling != null |
| && previousSibling.getNodeName().equalsIgnoreCase( |
| getTag()) && nextSibling != null |
| && nextSibling.getNodeName().equalsIgnoreCase(getTag())) { |
| previousSibling.appendChild(middleText); |
| while (nextSibling.getFirstChild() != null) { |
| previousSibling |
| .appendChild(nextSibling.getFirstChild()); |
| } |
| nextSibling.getParentNode().removeChild(nextSibling); |
| } else if (previousSibling != null |
| && previousSibling.getNodeName().equalsIgnoreCase( |
| getTag())) { |
| previousSibling.appendChild(middleText); |
| } else if (nextSibling != null |
| && nextSibling.getNodeName().equalsIgnoreCase(getTag())) { |
| nextSibling.insertBefore(middleText, nextSibling |
| .getFirstChild()); |
| } else { |
| Element bnode = createStyleElement(); |
| bnode.appendChild(middleText); |
| textNode.getParentNode().insertBefore(bnode, textNode); |
| } |
| textNode.getParentNode().removeChild(textNode); |
| } |
| } |
| |
| return new DOMRange(new DOMRefPosition(middleText, false), |
| new DOMRefPosition(middleText, true)); |
| } |
| |
| private boolean isEmptyString(String str) { |
| if (str == null || str.length() == 0) { |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * @return |
| */ |
| protected Element createStyleElement() { |
| if (_applyingNode != null) { |
| return _applyingNode; |
| } else { |
| Element element = getModel().getDocument().createElement(getTag()); |
| if (_cssProperty != null && _cssPropertyValue != null) { |
| element.setAttribute(_cssProperty, _cssPropertyValue); |
| } |
| return element; |
| } |
| } |
| |
| /** |
| * @param position |
| * @param b |
| * @return |
| */ |
| private IDOMPosition skip(IDOMPosition position, boolean forward) { |
| Node node = position.getSibling(forward); |
| |
| if (node == null) { |
| return position; |
| } |
| boolean canSkip = false; |
| if (node instanceof Text) { |
| canSkip = ((IDOMText) node).isElementContentWhitespace(); |
| } else if (node instanceof Element) { |
| if (getTag().equalsIgnoreCase(((Element) node).getTagName())) { |
| canSkip = true; |
| } else { |
| canSkip = false; |
| } |
| } else { |
| canSkip = true; |
| } |
| if (canSkip) { |
| return new DOMRefPosition(node, forward); |
| } else { |
| return position; |
| } |
| } |
| |
| /** |
| * @return Returns the _cssProperty. |
| */ |
| public final String getCssProperty() { |
| return _cssProperty; |
| } |
| |
| /** |
| * @param property |
| * The _cssProperty to set. |
| */ |
| public final void setCssProperty(String property) { |
| _cssProperty = property; |
| } |
| |
| /** |
| * @return Returns the _cssPropertyValue. |
| */ |
| public final String getCssPropertyValue() { |
| return _cssPropertyValue; |
| } |
| |
| /** |
| * @param propertyValue |
| * The _cssPropertyValue to set. |
| */ |
| public final void setCssPropertyValue(String propertyValue) { |
| _cssPropertyValue = propertyValue; |
| } |
| |
| /** |
| * @return Returns the _tag. |
| */ |
| public final String getTag() { |
| if (_tag != null) { |
| return _tag; |
| } else { |
| return _applyingNode.getNodeName(); |
| } |
| } |
| |
| /** |
| * @param _tag |
| * The _tag to set. |
| */ |
| public final void setTag(String _tag) { |
| this._tag = _tag; |
| } |
| |
| private void updateStyleElement() { |
| |
| } |
| } |