/***************************************************************************** * This file is part of Rinzo * * Author: Claudio Cancinos * WWW: https://sourceforge.net/projects/editorxml * Copyright (C): 2008, Claudio Cancinos * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; If not, see <http://www.gnu.org/licenses/> ****************************************************************************/ package ar.com.tadp.xml.rinzo.core.actions; import java.util.ArrayList; import java.util.List; import org.apache.commons.lang.StringUtils; import org.eclipse.jface.action.IAction; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.jface.viewers.ISelection; import org.eclipse.swt.graphics.Point; import org.eclipse.ui.IEditorActionDelegate; import org.eclipse.ui.IEditorPart; import ar.com.tadp.xml.rinzo.XMLEditorPlugin; import ar.com.tadp.xml.rinzo.core.RinzoXMLEditor; import ar.com.tadp.xml.rinzo.core.model.XMLNode; import ar.com.tadp.xml.rinzo.core.model.visitor.StringGeneratorVisitor; import ar.com.tadp.xml.rinzo.core.model.visitor.ToStringVisitor; import ar.com.tadp.xml.rinzo.core.utils.FileUtils; /** * An abstract class containing the template behavior for all actions who wants * to modify the content of the current edited document by using a * {@link StringGeneratorVisitor}. * * @author ccancinos */ public abstract class VisitorAction implements IEditorActionDelegate { private RinzoXMLEditor editor; public void setActiveEditor(IAction action, IEditorPart targetEditor) { this.editor = (RinzoXMLEditor) targetEditor; } public void run(IAction action) { try { IDocument document = this.editor.getDocumentProvider().getDocument(this.editor.getEditorInput()); String lineSeparator = this.editor.getLineSeparator(); this.editor.getModel().createTree(document); XMLNode rootNode = this.editor.getModel().getTree().getRootNode(); ISourceViewer sourceViewerEditor = editor.getSourceViewerEditor(); Point selectedRange = sourceViewerEditor.getSelectedRange(); StringGeneratorVisitor visitor = getVisitor(); if (selectedRange.y <= 0) { runOnFullDocument(document, lineSeparator, rootNode, visitor); } else { runOnSelection(document, lineSeparator, rootNode, selectedRange, visitor); } } catch (BadLocationException exception) { throw new RuntimeException(exception); } } private void runOnFullDocument(IDocument document, String lineSeparator, XMLNode rootNode, StringGeneratorVisitor visitor) throws BadLocationException { if (rootNode != null) { int prevWitespacesNumberOnLine = getPrevWitespacesNumberOnLine(document.get(), rootNode); int initialOffset = rootNode.getOffset() - prevWitespacesNumberOnLine; int length = rootNode.getCorrespondingNode().getOffset() + rootNode.getCorrespondingNode().getLength(); if (visitor instanceof ToStringVisitor) { ((ToStringVisitor) visitor).setLineSeparator(lineSeparator); } visitor.reset(); rootNode.accept(visitor); document.replace(initialOffset, length - initialOffset, visitor.getString()); this.editor.getModel().createTree(document); } } private void runOnSelection(IDocument document, String lineSeparator, XMLNode rootNode, Point selectedRange, StringGeneratorVisitor visitor) throws BadLocationException { List<XMLNode> selectedNodes = getNodesBySelection(selectedRange.x, selectedRange.y, rootNode); XMLNode selectedNode = null; if (selectedNodes != null) { selectedNode = selectedNodes.get(0); } if (selectedNode != null) { XMLNode lastNode = selectedNodes.get(selectedNodes.size() - 1); int originOffset = selectedNode.getOffset(); int originLength; XMLNode lastCorrespondingNode = lastNode.getCorrespondingNode(); if (lastCorrespondingNode == null) { originLength = lastNode.getOffset() + lastNode.getLength() - originOffset; } else { originLength = lastCorrespondingNode.getOffset() + lastCorrespondingNode.getLength() - originOffset; } int replaceOffset; int replaceLength; int selectOffset; int indent = 0; String insertPrefix = ""; XMLNode previousSibling = getPreviosSibling(selectedNode); if (previousSibling != null) { // case: (<prevSiblingTag>[whitespaces]</prevSiblingTag>|prevSiblingText[whitespaces])<selectedTag> // or // case: <?xml version="1.0" encoding="UTF-8"?>\n[whitespaces]<selectedTag> if (!previousSibling.isPiTag()) { // case: (<prevSiblingTag>[whitespaces]</prevSiblingTag>|prevSiblingText[whitespaces])<selectedTag> XMLNode previousCorrespondingNode = previousSibling.getCorrespondingNode(); int prevOffset; int prevLength; if (previousCorrespondingNode == null) { prevOffset = previousSibling.getOffset(); prevLength = previousSibling.getLength(); } else { prevOffset = previousCorrespondingNode.getOffset(); prevLength = previousCorrespondingNode.getLength(); } if (document.getLineOfOffset(prevOffset + prevLength) == document.getLineOfOffset(selectedNode.getOffset())) { // case: (<prevSiblingTag></prevSiblingTag>|prevSiblingText)[no_line_feed_whitespaces]<selectedTag> replaceOffset = prevOffset + prevLength; insertPrefix = lineSeparator; selectOffset = replaceOffset + insertPrefix.length(); replaceLength = originLength + (originOffset - replaceOffset); } else { // case: (<prevSiblingTag></prevSiblingTag>|prevSiblingText)[line_feed_whitespaces]<selectedTag> int prevWitespacesNumber = getPrevWitespacesNumberOnLine(document.get(), selectedNode); replaceOffset = originOffset - prevWitespacesNumber; replaceLength = originLength + prevWitespacesNumber; selectOffset = replaceOffset; } indent = getIndent(document, previousSibling); } else { // case: <?xml version="1.0" encoding="UTF-8"?>\n[whitespaces]<selectedTag> int prevWitespacesNumber = getPrevWitespacesNumberOnLine(document.get(), selectedNode); replaceOffset = originOffset - prevWitespacesNumber; replaceLength = originLength + prevWitespacesNumber; selectOffset = replaceOffset; } } else { // case: <prevTag>[whitespaces]<selectedTag> XMLNode parentNode = selectedNode.getParent(); if (document.getLineOfOffset(parentNode.getOffset() + parentNode.getLength()) == document.getLineOfOffset(selectedNode.getOffset())) { // case: <prevTag>[no_line_feed_whitespaces]<selectedTag> replaceOffset = parentNode.getOffset() + parentNode.getLength(); insertPrefix = lineSeparator; selectOffset = replaceOffset + insertPrefix.length(); replaceLength = originLength + (originOffset - replaceOffset); } else { // case: <prevTag>[line_feed_whitespaces]<selectedTag> int prevWitespacesNumber = getPrevWitespacesNumberOnLine(document.get(), selectedNode); replaceOffset = originOffset - prevWitespacesNumber; replaceLength = originLength + prevWitespacesNumber; selectOffset = replaceOffset; } indent = getIndent(document, parentNode) + 1; // parent indent + 1 } // format all selected siblings and separate them by line StringBuilder formatedXml = new StringBuilder(insertPrefix); if (visitor instanceof ToStringVisitor) { ((ToStringVisitor) visitor).setLineSeparator(lineSeparator); } for (int i = 0; i < selectedNodes.size(); i++) { if (i > 0) { formatedXml.append(lineSeparator); } visitor.reset(); if (visitor instanceof ToStringVisitor) { ((ToStringVisitor) visitor).setAddIndentation(indent); } XMLNode doNode = selectedNodes.get(i); doNode.accept(visitor); formatedXml.append(visitor.getString()); } document.replace(replaceOffset, replaceLength, formatedXml.toString()); this.editor.getModel().createTree(document); editor.getSourceViewerEditor().setSelectedRange(selectOffset, formatedXml.length() - insertPrefix.length()); } } protected abstract StringGeneratorVisitor getVisitor(); public void selectionChanged(IAction action, ISelection selection) { } protected XMLNode getSelectedNode() { return this.editor.getModel().getTree().getActiveNode(); } private int getPrevWitespacesNumberOnLine(String str, XMLNode node) { if (!StringUtils.isEmpty(str)) { int offset = node.getOffset(); char ch; if (offset > 0) { do { offset--; ch = str.charAt(offset); } while (ch != '\r' && ch != '\n' && offset > 0); return node.offset - offset - 1; } return node.offset - offset; } else { return 0; } } /** * Number of indents from start of line to beginning of tag TODO: review it * * @param document * @param node * @return */ private int getIndent(IDocument document, XMLNode node) { String str = document.get(); String indentToken = XMLEditorPlugin.getDefault().getIndentToken(); try { int startOffset = document.getLineOffset(document.getLineOfOffset(node.getOffset())); String indentStr = str.substring(startOffset, node.getOffset()); if (indentToken.startsWith(FileUtils.TAB)) { int width = Integer.parseInt(XMLEditorPlugin.getDefault().getPreferenceStore().getString("tabWidth")); String replaceStr = new String(new char[width]).replace('\0', ' '); indentStr = indentStr.replace(replaceStr, FileUtils.TAB); indentStr = indentStr.replace(" ", ""); return indentStr.length(); } else { indentStr = indentStr.replace(FileUtils.TAB, indentToken); int indent = indentStr.length() / indentToken.length(); int mod = indentStr.length() % indentToken.length(); if (mod != 0) { indent++; // round upper } return indent; } } catch (BadLocationException e) { new RuntimeException(e); } return 3; } private XMLNode getPreviosSibling(XMLNode node) { XMLNode parentNode = node.getParent(); List<XMLNode> children = parentNode.getChildren(); XMLNode previousNode = children.get(0); if (previousNode.equals(node)) { return null; // no sibling } if (previousNode.isTextTag() && isWhitespace(previousNode.getContent())) { previousNode = null; } for (int i = 1; i < children.size(); i++) { XMLNode currentNode = children.get(i); if (currentNode.equals(node)) { return previousNode; } if (!currentNode.isTextTag() || !isWhitespace(currentNode.getContent())) { previousNode = currentNode; } } throw new RuntimeException("Not found previous sibling nor parent"); } private static XMLNode getNextNonspaceSibling(XMLNode node) { XMLNode parentNode = node.getParent(); List<XMLNode> children = parentNode.getChildren(); boolean found = false; for (int i = 0; i < children.size(); i++) { XMLNode currentNode = children.get(i); if (found) { if (!currentNode.isTextTag() || !isWhitespace(currentNode.getContent())) { return currentNode; } } else { if (currentNode.equals(node)) { found = true; } } } return null; } /** * True when there is no other char then whitespace Empty string is also * whitespace * * @param str * @return */ private static boolean isWhitespace(String str) { for (int i = 0; i < str.length(); i++) { if (!Character.isWhitespace(str.charAt(i))) { return false; } } return true; } private static List<XMLNode> getNodesBySelection(int startOffset, int length, XMLNode node) { if (node.isTextTag() && isWhitespace(node.getContent())) { return null; } if (startOffset < node.getOffset()) { if (startOffset + length > node.getOffset()) { ArrayList<XMLNode> nodesBySelection = new ArrayList<XMLNode>(); nodesBySelection.add(node); XMLNode nextNonspaceSibling = node; while ((nextNonspaceSibling = getNextNonspaceSibling(nextNonspaceSibling)) != null) { if (nextNonspaceSibling.getOffset() < startOffset + length) { nodesBySelection.add(nextNonspaceSibling); } else { break; } } return nodesBySelection; } return null; } else { if (startOffset < node.getOffset() + node.getLength()) { ArrayList<XMLNode> nodesBySelection = new ArrayList<XMLNode>(); nodesBySelection.add(node); XMLNode nextNonspaceSibling = node; while ((nextNonspaceSibling = getNextNonspaceSibling(nextNonspaceSibling)) != null) { if (nextNonspaceSibling.getOffset() < startOffset + length) { nodesBySelection.add(nextNonspaceSibling); } else { break; } } return nodesBySelection; } List<XMLNode> children = node.getChildren(); for (XMLNode xmlNode : children) { List<XMLNode> nodesBySelection = getNodesBySelection(startOffset, length, xmlNode); if (nodesBySelection != null) { return nodesBySelection; } } } return null; } }