/***************************************************************************** * 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; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.Map.Entry; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.contentassist.CompletionProposal; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext; import org.eclipse.jface.text.quickassist.IQuickAssistProcessor; import org.eclipse.jface.text.source.Annotation; import org.eclipse.jface.text.templates.DocumentTemplateContext; import org.eclipse.jface.text.templates.Template; import org.eclipse.jface.text.templates.TemplateContext; import org.eclipse.jface.text.templates.TemplateContextType; import org.eclipse.jface.text.templates.TemplateProposal; import org.eclipse.swt.dnd.Clipboard; import org.eclipse.swt.dnd.TextTransfer; import org.eclipse.swt.dnd.Transfer; import ar.com.tadp.xml.rinzo.XMLEditorPlugin; import ar.com.tadp.xml.rinzo.core.contentassist.proposals.XMLCompletionProposal; import ar.com.tadp.xml.rinzo.core.model.XMLAttribute; import ar.com.tadp.xml.rinzo.core.model.XMLNode; import ar.com.tadp.xml.rinzo.core.model.visitor.EscapeHTMLVisitor; import ar.com.tadp.xml.rinzo.core.model.visitor.EscapeVisitor; import ar.com.tadp.xml.rinzo.core.model.visitor.EscapeXMLVisitor; import ar.com.tadp.xml.rinzo.core.model.visitor.ToStringVisitor; /** * Generates proposals for quick assistance. * * @author ccancinos */ public class XMLCorrectionProcessor implements IQuickAssistProcessor { private final RinzoXMLEditor xmlEditor; private EscapeVisitor escapeHtmlVisitor = new EscapeHTMLVisitor(); private EscapeVisitor escapeXmlVisitor = new EscapeXMLVisitor(); private String lineSeparator = null; public XMLCorrectionProcessor(RinzoXMLEditor xmlEditor) { this.xmlEditor = xmlEditor; } public ICompletionProposal[] computeQuickAssistProposals(IQuickAssistInvocationContext context) { try { Collection<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>(); XMLNode node = this.xmlEditor.getModel().getTree().getActiveNode(context.getOffset()); this.addTagProposals(context, proposals, node); this.addTextProposals(context, proposals, node); return proposals.toArray(new ICompletionProposal[proposals.size()]); } catch (BadLocationException e) { return null; } } private void addTextProposals(IQuickAssistInvocationContext context, Collection<ICompletionProposal> proposals, XMLNode node) { if (node.isTextTag()) { this.addCDATAProposal(proposals, node, context); this.addEscapeProposal(proposals, node, context); } } private void addTagProposals(IQuickAssistInvocationContext context, Collection<ICompletionProposal> proposals, XMLNode node) throws BadLocationException { if (node.isTag() || node.isEndTag() || node.isEmptyTag()) { if (node.isEndTag()) { node = node.getCorrespondingNode(); } int offset = node.getSelectionOffset(); int tagLength = this.getNodeFullLength(node); IDocument document = context.getSourceViewer().getDocument(); String replacement = document.get(node.getOffset(), tagLength); String tagIndentation = this.getIndentation(document, node.getOffset()); String indentationToken = XMLEditorPlugin.getDefault().getIndentToken(); this.addRenameTagProposal(proposals, node, offset, tagLength, replacement, document); this.addDuplicateTagProposal(proposals, node, offset, tagLength, replacement, document, tagIndentation); this.addCutTagProposal(proposals, node, context, tagLength, replacement); this.addSurroundWithTagProposal(proposals, node, offset, tagLength, replacement, document, tagIndentation, indentationToken); this.addCommentTagProposal(proposals, node, offset, tagLength, replacement, document, tagIndentation, indentationToken); this.addDeleteTagProposal(proposals, node, tagLength); this.addDeleteSurroundingTagProposal(proposals, node, tagLength); } } private void addCDATAProposal(Collection<ICompletionProposal> proposals, XMLNode node, IQuickAssistInvocationContext context) { int offset = node.getOffset() + 1; int tagLength = node.getContent().length(); String trimmedContent = node.getContent().trim(); String replacement = node.getContent().replace(trimmedContent, "<![CDATA[" + trimmedContent + "]]>"); this.addTemplate(proposals, "Surround With CDATA", replacement, PluginImages.IMG_CHANGE, context .getSourceViewer().getDocument(), offset, tagLength, offset, tagLength); } private void addEscapeProposal(Collection<ICompletionProposal> proposals, XMLNode node, IQuickAssistInvocationContext context) { node.accept(this.escapeHtmlVisitor); node.accept(this.escapeXmlVisitor); String replacementHtml = this.escapeHtmlVisitor.getString(); String replacementXml = this.escapeXmlVisitor.getString(); this.escapeHtmlVisitor.reset(); this.escapeXmlVisitor.reset(); int offset = node.getOffset() + 1; int tagLength = node.getContent().length(); this.addTemplate(proposals, "Escape HTML Characters", replacementHtml, PluginImages.IMG_CHANGE, context .getSourceViewer().getDocument(), offset, tagLength, offset, tagLength); this.addTemplate(proposals, "Escape XML Characters", replacementXml, PluginImages.IMG_CHANGE, context .getSourceViewer() .getDocument(), offset, tagLength, offset, tagLength); } private void addRenameTagProposal(Collection<ICompletionProposal> proposals, XMLNode node, int offset, int length, String replacement, IDocument document) { replacement = replacement.replace("<" + node.getTagName() + ">", "<${" + node.getTagName() + "}>"); replacement = replacement.replace("</" + node.getTagName() + ">", "</${" + node.getTagName() + "}>"); this.addTemplate(proposals, "Rename Tag", replacement, PluginImages.IMG_EDIT_INLINE, document, offset, length, offset, length); } private void addDuplicateTagProposal(Collection<ICompletionProposal> proposals, XMLNode node, int offset, int length, String replacement, IDocument document, String tagIndentation) { for (Iterator<Entry<String, XMLAttribute>> iterator = node.getAttributes().entrySet().iterator(); iterator .hasNext();) { Entry<String, XMLAttribute> attribute = iterator.next(); XMLAttribute attr = attribute.getValue(); replacement = replacement.replace("\"" + attr.getValue() + "\"", "\"${" + attr.getName() + "}\""); } replacement = this.getLineSeparator() + tagIndentation + replacement; this.addTemplate(proposals, "Duplicate Tag", replacement, PluginImages.IMG_CHANGE, document, offset + length, 0, offset + length, length); } private void addCutTagProposal(Collection<ICompletionProposal> proposals, final XMLNode node, final IQuickAssistInvocationContext context, final int length, final String replacement) { proposals.add(new XMLCompletionProposal("", node.getOffset(), length, 0, PluginImages .get(PluginImages.IMG_CHANGE), "Cut Tag", null, null) { public void apply(IDocument document) { Clipboard clipboard = null; try { clipboard = new Clipboard(xmlEditor.getSite().getShell().getDisplay()); clipboard.setContents(new Object[] { replacement }, new Transfer[] { TextTransfer.getInstance() }); } finally { if (clipboard != null) { clipboard.dispose(); } } super.apply(document); } }); } private void addSurroundWithTagProposal(Collection<ICompletionProposal> proposals, XMLNode node, int offset, int length, String replacement, IDocument document, String tagIndentation, String indentationToken) { replacement = "<${element}>" + this.getLineSeparator() + tagIndentation + indentationToken + replacement.replace(this.getLineSeparator(), this.getLineSeparator() + indentationToken) + this.getLineSeparator() + tagIndentation + "</${element}>"; this.addTemplate(proposals, "Surround With Tag", replacement, PluginImages.IMG_CHANGE, document, offset, length, offset, length); } private void addDeleteSurroundingTagProposal(Collection<ICompletionProposal> proposals, XMLNode node, int length) { if (!node.isEmptyTag()) { ToStringVisitor visitor = new ToStringVisitor(); node.accept(visitor); String replacement = visitor.getString(); replacement = replacement.substring(replacement.indexOf(">") + 1, replacement.lastIndexOf("</")); proposals.add(new CompletionProposal(replacement, node.getOffset(), length, 0, PluginImages .get(PluginImages.IMG_DELETE), "Delete Surrounding Tag", null, "Deletes this tag leaving its childs.")); } } private void addCommentTagProposal(Collection<ICompletionProposal> proposals, XMLNode node, int offset, int length, String replacement, IDocument document, String tagIndentation, String indentationToken) { replacement = "<!--" + this.getLineSeparator() + tagIndentation + replacement + this.getLineSeparator() + tagIndentation + "-->"; proposals.add(new CompletionProposal(replacement, node.getOffset(), length, 0, PluginImages .get(PluginImages.IMG_CHANGE), "Comment Tag", null, null)); } private String getLineSeparator() { if (this.lineSeparator == null) { this.lineSeparator = this.xmlEditor.getLineSeparator(); } return this.lineSeparator; } private void addDeleteTagProposal(Collection<ICompletionProposal> proposals, XMLNode node, int length) { proposals.add(new CompletionProposal("", node.getOffset(), length, 0, PluginImages.get(PluginImages.IMG_DELETE), "Delete Tag", null, null)); } private void addTemplate(Collection<ICompletionProposal> proposals, String name, String pattern, String image, IDocument document, int positionOffset, int positionLength, int regionOffset, int regionLength) { TemplateContext context = new DocumentTemplateContext(new TemplateContextType(), document, new Position( positionOffset - 1, positionLength)); IRegion region = new Region(regionOffset, regionLength); Template template = new Template(name, "", "tag", pattern, false); TemplateProposal templateProposal = new TemplateProposal(template, context, region, PluginImages.get(image)); proposals.add(templateProposal); } private int getNodeFullLength(XMLNode node) { return (node.isEmptyTag()) ? node.getLength() : node.getCorrespondingNode().getOffset() - node.getOffset() + node.getCorrespondingNode().getLength(); } private String getIndentation(IDocument document, int offset) { try { int line = document.getLineOfOffset(offset); int lineOffset = document.getLineOffset(line); return this.getLeadingWhitespace(document.get(lineOffset, Math.abs(lineOffset - offset))); } catch (BadLocationException e) { return ""; } } public String getErrorMessage() { return null; } public boolean canFix(Annotation annotation) { return true; } public boolean canAssist(IQuickAssistInvocationContext invocationContext) { return true; } private String getLeadingWhitespace(String str) { if (str == null) { return ""; } int sz = str.length(); StringBuffer buffer = new StringBuffer(); for (int i = 0; i < sz; i++) { if ((Character.isWhitespace(str.charAt(i)))) { buffer.append(str.charAt(i)); } else { break; } } return buffer.toString(); } }