| /******************************************************************************* |
| * 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.Arrays; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.gef.EditPart; |
| import org.eclipse.jst.pagedesigner.IHTMLConstants; |
| import org.eclipse.jst.pagedesigner.dom.DOMPosition; |
| import org.eclipse.jst.pagedesigner.dom.DOMPositionHelper; |
| 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.EditHelper; |
| import org.eclipse.jst.pagedesigner.dom.EditModelQuery; |
| import org.eclipse.jst.pagedesigner.dom.IDOMPosition; |
| import org.eclipse.jst.pagedesigner.dom.IDOMRefPosition; |
| import org.eclipse.jst.pagedesigner.parts.TextEditPart; |
| import org.eclipse.jst.pagedesigner.viewer.IHTMLGraphicalViewer; |
| import org.eclipse.wst.sse.core.internal.provisional.INodeNotifier; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| import org.w3c.dom.Text; |
| |
| /** |
| * @author mengbo |
| */ |
| public class ParagraphApplyStyleCommand extends ApplyStyleCommand { |
| |
| /** |
| * the list of possible html heading tags |
| */ |
| private static final String[] HH = { "h1", "h2", "h3", "h4", "h5", "h6" }; |
| |
| /** |
| * @param viewer |
| * @param tag |
| * @param property |
| * @param value |
| */ |
| public ParagraphApplyStyleCommand(IHTMLGraphicalViewer viewer, String tag, |
| String property, String value) { |
| super(viewer, tag, property, value); |
| } |
| |
| /** |
| * @param viewer |
| * @param node |
| * @param property |
| * @param value |
| */ |
| public ParagraphApplyStyleCommand(IHTMLGraphicalViewer viewer, |
| Element node, String property, String value) { |
| super(viewer, node, property, value); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jst.pagedesigner.commands.range.RangeModeCommand#doRangeExecute(org.eclipse.jst.pagedesigner.dom.DOMRange) |
| */ |
| protected DOMRange doRangeExecute(DOMRange range) { |
| if (range != null) { |
| boolean ordered = range.isOrdered(); |
| IDOMPosition start = ordered ? range.getStartPosition() : range |
| .getEndPosition(); |
| IDOMPosition end = ordered ? range.getEndPosition() : range |
| .getStartPosition(); |
| Node common = null; |
| Node container = null; |
| if (EditModelQuery.isSame(range)) { |
| container = start.getContainerNode(); |
| ParagraphFinder finder = new ParagraphFinder(start); |
| Paragraph p = finder.getParagraph(start); |
| start = p.getStart(); |
| end = p.getEnd(); |
| common = p.getLowestContainer(); |
| } else { |
| common = EditModelQuery.getInstance().getCommonAncestor(start, |
| end); |
| } |
| DOMRange rt; |
| // This code is for h1-h6 only, it may need to be replaced. |
| if ((rt = replaceExistingH(start, end)) != null) { |
| return rt; |
| } |
| // replace existing p |
| if (getTag().equalsIgnoreCase(IHTMLConstants.TAG_P)) { |
| rt = replaceExistingP(start, end); |
| if (rt != null) { |
| return rt; |
| } |
| } |
| if (start.getContainerNode() == end.getContainerNode()) { |
| int offset1 = start.getOffset(); |
| int offset2 = end.getOffset(); |
| IDOMPosition old = start; |
| start = split(start); |
| // parent is splited |
| if (start != old) { |
| container = start.getNextSiblingNode(); |
| offset2 -= offset1; |
| end = new DOMPosition(container, offset2); |
| } |
| end = split(end); |
| } else { |
| start = split(common, start); |
| end = split(common, end); |
| } |
| range = InsertStyleTag(new DOMRange(start, end)); |
| } |
| return range; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.gef.commands.Command#canExecute() |
| */ |
| public boolean canExecute() { |
| return true; |
| } |
| |
| /* |
| * Try to split the node so that we can avoid wrap its children directly. |
| * Begining from 'position' the split can reach as high as the level of |
| * 'common'. |
| */ |
| private IDOMPosition split(Node common, IDOMPosition position) { |
| Assert.isTrue(EditModelQuery.isChild(common, position |
| .getContainerNode())); |
| Node container = position.getContainerNode(); |
| String[] styleNodes = new String[EditModelQuery.HTML_STYLE_NODES.size()]; |
| EditModelQuery.HTML_STYLE_NODES.toArray(styleNodes); |
| while (EditModelQuery.isText(container) || (container != common && // |
| EditModelQuery.containItem(styleNodes, container, true))) { |
| IDOMPosition old = position; |
| position = EditHelper.splitNode(position); |
| if (old == position) { |
| int pos = EditHelper.getLocation(position); |
| switch (pos) { |
| case -1: |
| position = new DOMRefPosition(position.getContainerNode(), |
| false); |
| break; |
| case 1: |
| position = new DOMRefPosition(position.getContainerNode(), |
| true); |
| } |
| } |
| Node containerBackup = container; |
| container = container.getParentNode(); |
| if (containerBackup.getNodeName().equalsIgnoreCase( |
| IHTMLConstants.TAG_P)) { |
| container.removeChild(containerBackup); |
| } |
| } |
| return position; |
| } |
| |
| /* |
| * Split the position's container node only. |
| */ |
| private IDOMPosition split(IDOMPosition position) { |
| Node container = position.getContainerNode(); |
| String[] styleNodes = new String[EditModelQuery.HTML_STYLE_NODES.size()]; |
| EditModelQuery.HTML_STYLE_NODES.toArray(styleNodes); |
| if (EditModelQuery.isText(container) |
| || EditModelQuery.containItem(styleNodes, container, true)) { |
| return EditHelper.splitNode(position); |
| } |
| return position; |
| } |
| |
| private DOMRange replaceExistingH(IDOMPosition start, IDOMPosition end) { |
| Node common = EditModelQuery.getInstance() |
| .getCommonAncestor(start, end); |
| // Here we insert some code to avoid creating tags duplicated. but these |
| // are not the entire cases. |
| if (Arrays.asList(HH).contains( |
| getAName(getTag()).toLowerCase()) |
| && Arrays.asList(HH).contains( |
| getAName(common.getNodeName()).toLowerCase())) { |
| // uncheck action menu |
| if (getAName(getTag()).toLowerCase().equalsIgnoreCase( |
| getAName(common.getNodeName()).toLowerCase())) { |
| NodeList nodes = common.getChildNodes(); |
| |
| for (int i = 0, size = nodes.getLength(); i < size; i++) { |
| common.getParentNode().insertBefore(nodes.item(i), common); |
| } |
| common.getParentNode().removeChild(common); |
| return new DOMRange(start, end); |
| } |
| start = DOMPositionHelper.toDOMRefPosition(start); |
| end = DOMPositionHelper.toDOMRefPosition(end); |
| Node newHNode = EditModelQuery.getDocumentNode(common) |
| .createElement(getTag()); |
| EditModelQuery.copyChildren(common, newHNode); |
| common.getParentNode().replaceChild(newHNode, common); |
| return new DOMRange(start, end); |
| } |
| return null; |
| } |
| |
| private DOMRange replaceExistingP(IDOMPosition start, IDOMPosition end) { |
| // find the selected startNode,endNode and start node's parent node |
| Node startNode = start instanceof IDOMRefPosition ? start |
| .getNextSiblingNode() : start.getContainerNode(); |
| Node endNode = end instanceof IDOMRefPosition ? end |
| .getPreviousSiblingNode() : end.getContainerNode(); |
| Node parentNode = startNode.getParentNode(); |
| |
| if (!(start.isText()) && start instanceof DOMPosition) { |
| startNode = startNode.getChildNodes().item(start.getOffset()); |
| parentNode = start.getContainerNode(); |
| } |
| if (!(end.isText()) && end instanceof DOMPosition) { |
| // because the offset is based on the position between nodes,so we |
| // need to reduce one from the offset |
| // in order to get the correct end node. |
| endNode = endNode.getChildNodes().item(end.getOffset() - 1); |
| } |
| |
| // compute selected character number in the text or selected element |
| // number under a node |
| int len = 0; |
| // if (start instanceof DOMPosition && end instanceof DOMPosition |
| // || start instanceof IDOMPosition && end instanceof IDOMPosition) { |
| // TODO: as written, this will be the only statement run, since |
| // both start and end are instanceof IDOMPosition by defn. |
| len = end.getOffset() - start.getOffset(); |
| // } else { |
| // IDOMRefPosition startRef = null; |
| // IDOMRefPosition endRef = null; |
| // if (!(start.isText()) && start instanceof DOMPosition) { |
| // startRef = new DOMRefPosition(startNode, false); |
| // } else if (!(end.isText()) && end instanceof DOMPosition) { |
| // endRef = new DOMRefPosition(endNode, true); |
| // } |
| // len = (endRef != null ? endRef.getOffset() : end.getOffset()) |
| // - (startRef != null ? startRef.getOffset() : start |
| // .getOffset()); |
| // } |
| |
| // if a full Text node is selected,and the Text node is the only child |
| // of its parent |
| if ((startNode == endNode) && (startNode instanceof Text)) { |
| TextEditPart part = (TextEditPart) ((INodeNotifier) startNode) |
| .getAdapterFor(EditPart.class); |
| boolean condition = false; |
| if (start instanceof IDOMRefPosition |
| || (start instanceof DOMPosition && !start.isText())) { |
| condition = parentNode.getNodeName().equalsIgnoreCase( |
| IHTMLConstants.TAG_P) |
| && parentNode.getChildNodes().getLength() == 1; |
| } else { |
| condition = parentNode.getNodeName().equalsIgnoreCase( |
| IHTMLConstants.TAG_P) |
| && parentNode.getChildNodes().getLength() == 1 |
| && part.getTextData().length() == len; |
| } |
| if (condition) { |
| // if uncheck the align action |
| if (this._applyingNode |
| .getAttribute(IHTMLConstants.ATTR_ALIGN) |
| .equals( |
| ((Element) parentNode) |
| .getAttribute(IHTMLConstants.ATTR_ALIGN))) { |
| ((Element) parentNode) |
| .removeAttribute(IHTMLConstants.ATTR_ALIGN); |
| IDOMPosition startPos = new DOMPosition(parentNode, 0); |
| IDOMPosition endPos = new DOMRefPosition(endNode, true); |
| return new DOMRange(startPos, endPos); |
| } |
| // else replace the align attribute |
| /** |
| * this._applyingNode.appendChild(startNode); |
| * parentNode.getParentNode().replaceChild(this._applyingNode, |
| * parentNode); |
| */ |
| String align = this._applyingNode |
| .getAttribute(IHTMLConstants.ATTR_ALIGN); |
| ((Element) parentNode).setAttribute(IHTMLConstants.ATTR_ALIGN, |
| align); |
| |
| IDOMPosition startPos = new DOMPosition(parentNode, 0); |
| IDOMPosition endPos = new DOMRefPosition(endNode, true); |
| return new DOMRange(startPos, endPos); |
| } |
| } else { |
| if (parentNode != null |
| && parentNode.getNodeName().equalsIgnoreCase( |
| IHTMLConstants.TAG_P) |
| && parentNode.getChildNodes().getLength() == len) { |
| if (this._applyingNode |
| .getAttribute(IHTMLConstants.ATTR_ALIGN) |
| .equals( |
| ((Element) parentNode) |
| .getAttribute(IHTMLConstants.ATTR_ALIGN))) { |
| ((Element) parentNode) |
| .removeAttribute(IHTMLConstants.ATTR_ALIGN); |
| IDOMPosition startPos = new DOMPosition(parentNode, 0); |
| IDOMPosition endPos = new DOMRefPosition(endNode, true); |
| return new DOMRange(startPos, endPos); |
| } |
| |
| /** |
| * Node sibling = startNode.getNextSibling(); |
| * this._applyingNode.appendChild(startNode); Node |
| * endNodeSibling = endNode.getNextSibling(); while (sibling != |
| * null && startNode != endNode && sibling != endNodeSibling) { |
| * Node tempNode = sibling.getNextSibling(); |
| * this._applyingNode.appendChild(sibling); sibling = tempNode; } |
| * parentNode.getParentNode().replaceChild(this._applyingNode, |
| * parentNode); |
| */ |
| String align = this._applyingNode |
| .getAttribute(IHTMLConstants.ATTR_ALIGN); |
| ((Element) parentNode).setAttribute(IHTMLConstants.ATTR_ALIGN, |
| align); |
| |
| IDOMPosition startPos = new DOMPosition(parentNode, 0); |
| IDOMPosition endPos = new DOMRefPosition(endNode, true); |
| return new DOMRange(startPos, endPos); |
| } |
| } |
| return null; |
| } |
| |
| private DOMRange InsertStyleTag(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 (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(); |
| } |
| |
| // now the startContainer and the endContainer should share the same |
| // parent |
| Element newNode = createStyleElement(); |
| startContainer.getParentNode() |
| .insertBefore(newNode, startContainer); |
| |
| Node sibling = startContainer.getNextSibling(); |
| newNode.appendChild(startContainer); |
| Node endNodeSibling = endContainer.getNextSibling(); |
| while (sibling != null && startContainer != endContainer |
| && sibling != endNodeSibling) { |
| Node tempNode = sibling.getNextSibling(); |
| newNode.appendChild(sibling); |
| sibling = tempNode; |
| } |
| |
| IDOMPosition startPos = new DOMPosition(newNode, 0); |
| IDOMPosition endPos = new DOMRefPosition(endContainer, true); |
| return new DOMRange(startPos, endPos); |
| } |
| |
| private static String getAName(String name) { |
| return name == null ? "" : name; |
| } |
| } |