summaryrefslogtreecommitdiff
path: root/platform/structuralsearch/source/com/intellij/structuralsearch/XmlStructuralSearchProfile.java
diff options
context:
space:
mode:
Diffstat (limited to 'platform/structuralsearch/source/com/intellij/structuralsearch/XmlStructuralSearchProfile.java')
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/XmlStructuralSearchProfile.java229
1 files changed, 229 insertions, 0 deletions
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/XmlStructuralSearchProfile.java b/platform/structuralsearch/source/com/intellij/structuralsearch/XmlStructuralSearchProfile.java
new file mode 100644
index 000000000000..5e0ca8bde957
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/XmlStructuralSearchProfile.java
@@ -0,0 +1,229 @@
+package com.intellij.structuralsearch;
+
+import com.intellij.codeInsight.template.TemplateContextType;
+import com.intellij.codeInsight.template.XmlContextType;
+import com.intellij.lang.Language;
+import com.intellij.lang.StdLanguages;
+import com.intellij.lang.xml.XMLLanguage;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.fileTypes.StdFileTypes;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.*;
+import com.intellij.psi.xml.XmlDocument;
+import com.intellij.psi.xml.XmlFile;
+import com.intellij.psi.xml.XmlTag;
+import com.intellij.psi.xml.XmlText;
+import com.intellij.structuralsearch.impl.matcher.*;
+import com.intellij.structuralsearch.impl.matcher.compiler.GlobalCompilingVisitor;
+import com.intellij.structuralsearch.impl.matcher.compiler.XmlCompilingVisitor;
+import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter;
+import com.intellij.structuralsearch.impl.matcher.filters.XmlLexicalNodesFilter;
+import com.intellij.structuralsearch.plugin.replace.ReplaceOptions;
+import com.intellij.structuralsearch.plugin.replace.ReplacementInfo;
+import com.intellij.structuralsearch.plugin.replace.impl.ReplacementContext;
+import com.intellij.structuralsearch.plugin.replace.impl.Replacer;
+import com.intellij.structuralsearch.plugin.replace.impl.ReplacerUtil;
+import com.intellij.structuralsearch.plugin.ui.Configuration;
+import com.intellij.util.IncorrectOperationException;
+import com.intellij.util.LocalTimeCounter;
+import com.intellij.xml.util.HtmlUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import static com.intellij.structuralsearch.PredefinedConfigurationUtil.createSearchTemplateInfo;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public class XmlStructuralSearchProfile extends StructuralSearchProfile {
+
+ private XmlLexicalNodesFilter myLexicalNodesFilter;
+
+ public void compile(PsiElement[] elements, @NotNull GlobalCompilingVisitor globalVisitor) {
+ elements[0].getParent().accept(new XmlCompilingVisitor(globalVisitor));
+ }
+
+ @NotNull
+ public PsiElementVisitor createMatchingVisitor(@NotNull GlobalMatchingVisitor globalVisitor) {
+ return new XmlMatchingVisitor(globalVisitor);
+ }
+
+ @NotNull
+ @Override
+ public PsiElementVisitor getLexicalNodesFilter(@NotNull LexicalNodesFilter filter) {
+ if (myLexicalNodesFilter == null) {
+ myLexicalNodesFilter = new XmlLexicalNodesFilter(filter);
+ }
+ return myLexicalNodesFilter;
+ }
+
+ @NotNull
+ public CompiledPattern createCompiledPattern() {
+ return new XmlCompiledPattern();
+ }
+
+ @Override
+ public boolean canProcess(@NotNull FileType fileType) {
+ return fileType == StdFileTypes.XML || fileType == StdFileTypes.HTML || fileType == StdFileTypes.JSP ||
+ fileType == StdFileTypes.JSPX || fileType == StdFileTypes.XHTML;
+ }
+
+ public boolean isMyLanguage(@NotNull Language language) {
+ return language instanceof XMLLanguage;
+ }
+
+ @NotNull
+ @Override
+ public PsiElement[] createPatternTree(@NotNull String text,
+ @NotNull PatternTreeContext context,
+ @NotNull FileType fileType,
+ @Nullable Language language,
+ String contextName, @Nullable String extension,
+ @NotNull Project project,
+ boolean physical) {
+ final String ext = extension != null ? extension : fileType.getDefaultExtension();
+ String text1 = context == PatternTreeContext.File ? text : "<QQQ>" + text + "</QQQ>";
+ final PsiFile fileFromText = PsiFileFactory.getInstance(project)
+ .createFileFromText("dummy." + ext, fileType, text1, LocalTimeCounter.currentTime(), physical, true);
+
+ final XmlDocument document = HtmlUtil.getRealXmlDocument(((XmlFile)fileFromText).getDocument());
+ if (context == PatternTreeContext.File) {
+ return new PsiElement[]{document};
+ }
+
+ return document.getRootTag().getValue().getChildren();
+ }
+
+ @Override
+ public Class<? extends TemplateContextType> getTemplateContextTypeClass() {
+ return XmlContextType.class;
+ }
+
+ @NotNull
+ @Override
+ public FileType detectFileType(@NotNull PsiElement context) {
+ PsiFile file = context instanceof PsiFile ? (PsiFile)context : context.getContainingFile();
+ Language contextLanguage = context instanceof PsiFile ? null : context.getLanguage();
+ if (file.getLanguage() == StdLanguages.HTML || (file.getFileType() == StdFileTypes.JSP && contextLanguage == StdLanguages.HTML)) {
+ return StdFileTypes.HTML;
+ }
+ return StdFileTypes.XML;
+ }
+
+ @Override
+ public void checkReplacementPattern(Project project, ReplaceOptions options) {
+ }
+
+ @Override
+ public StructuralReplaceHandler getReplaceHandler(@NotNull ReplacementContext context) {
+ return new MyReplaceHandler(context);
+ }
+
+ private static class MyReplaceHandler extends StructuralReplaceHandler {
+ private final ReplacementContext myContext;
+
+ private MyReplaceHandler(ReplacementContext context) {
+ myContext = context;
+ }
+
+ public void replace(ReplacementInfo info, ReplaceOptions options) {
+ PsiElement elementToReplace = info.getMatch(0);
+ assert elementToReplace != null;
+ PsiElement elementParent = elementToReplace.getParent();
+ String replacementToMake = info.getReplacement();
+ boolean listContext = elementToReplace.getParent() instanceof XmlTag;
+
+ if (listContext) {
+ doReplaceInContext(info, elementToReplace, replacementToMake, elementParent, myContext);
+ }
+
+ PsiElement[] statements = ReplacerUtil.createTreeForReplacement(replacementToMake, PatternTreeContext.Block, myContext);
+
+ if (statements.length > 0) {
+ PsiElement replacement = ReplacerUtil.copySpacesAndCommentsBefore(elementToReplace, statements, replacementToMake, elementParent);
+
+ // preserve comments
+ Replacer.handleComments(elementToReplace, replacement, myContext);
+ elementToReplace.replace(replacement);
+ }
+ else {
+ final PsiElement nextSibling = elementToReplace.getNextSibling();
+ elementToReplace.delete();
+ assert nextSibling != null;
+ if (nextSibling.isValid()) {
+ if (nextSibling instanceof PsiWhiteSpace) {
+ nextSibling.delete();
+ }
+ }
+ }
+ }
+ }
+
+ private static void doReplaceInContext(ReplacementInfo info,
+ PsiElement elementToReplace,
+ String replacementToMake,
+ PsiElement elementParent,
+ ReplacementContext context) {
+ PsiElement[] statements = ReplacerUtil.createTreeForReplacement(replacementToMake, PatternTreeContext.Block, context);
+
+ if (statements.length > 1) {
+ elementParent.addRangeBefore(statements[0], statements[statements.length - 1], elementToReplace);
+ }
+ else if (statements.length == 1) {
+ PsiElement replacement = statements[0];
+
+ Replacer.handleComments(elementToReplace, replacement, context);
+
+ try {
+ elementParent.addBefore(replacement, elementToReplace);
+ }
+ catch (IncorrectOperationException e) {
+ elementToReplace.replace(replacement);
+ }
+ }
+
+ final int matchSize = info.getMatchesCount();
+
+ for (int i = 0; i < matchSize; ++i) {
+ PsiElement element = info.getMatch(i);
+
+ if (element == null) continue;
+ PsiElement firstToDelete = element;
+ PsiElement lastToDelete = element;
+ PsiElement prevSibling = element.getPrevSibling();
+ PsiElement nextSibling = element.getNextSibling();
+
+ if (prevSibling instanceof PsiWhiteSpace) {
+ firstToDelete = prevSibling;
+ }
+ else if (prevSibling == null && nextSibling instanceof PsiWhiteSpace) {
+ lastToDelete = nextSibling;
+ }
+ if (nextSibling instanceof XmlText && i + 1 < matchSize) {
+ final PsiElement next = info.getMatch(i + 1);
+ if (next != null && next == nextSibling.getNextSibling()) {
+ lastToDelete = nextSibling;
+ }
+ }
+ element.getParent().deleteChildRange(firstToDelete, lastToDelete);
+ }
+ }
+
+ @Override
+ Configuration[] getPredefinedTemplates() {
+ return XmlPredefinedConfigurations.createPredefinedTemplates();
+ }
+
+ private static class XmlPredefinedConfigurations {
+ private static final String HTML_XML = SSRBundle.message("xml_html.category");
+
+ private static Configuration[] createPredefinedTemplates() {
+ return new Configuration[]{
+ createSearchTemplateInfo("xml tag", "<'a/>", HTML_XML, StdFileTypes.XML),
+ createSearchTemplateInfo("xml attribute", "<'_tag 'attribute=\"'_value\"/>", HTML_XML, StdFileTypes.XML),
+ createSearchTemplateInfo("xml attribute value", "<'_tag '_attribute=\"'value\"/>", HTML_XML, StdFileTypes.XML),
+ createSearchTemplateInfo("xml/html tag value", "<table>'_content*</table>", HTML_XML, StdFileTypes.HTML),
+ };
+ }
+ }
+}