/* * Copyright 2002-2005 Sascha Weinreuter * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.intellij.plugins.xpathView; import com.intellij.openapi.diagnostic.Logger; import com.intellij.psi.PsiElement; import com.intellij.psi.XmlElementVisitor; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.xml.*; import com.intellij.xml.XmlAttributeDescriptor; import org.intellij.plugins.xpathView.support.XPathSupport; import org.intellij.plugins.xpathView.support.jaxen.PsiDocumentNavigator; import org.intellij.plugins.xpathView.util.MyPsiUtil; import org.intellij.plugins.xpathView.util.Namespace; import org.jaxen.JaxenException; import org.jaxen.XPath; import org.jetbrains.annotations.Nullable; import java.util.HashMap; import java.util.List; import java.util.Map; public class XPathExpressionGenerator { private static final XPathSupport xpathSupport = XPathSupport.getInstance(); private XPathExpressionGenerator() { } public static String getUniquePath(XmlElement element, XmlTag context) { final PathVisitor visitor = new PathVisitor(context); element.accept(visitor); return visitor.getUniquePath(); } public static String getPath(XmlElement element, XmlTag context) { final PathVisitor visitor = new PathVisitor(context); element.accept(visitor); return visitor.getPath(); } @Nullable public static PsiElement transformToValidShowPathNode(PsiElement contextNode) { PsiElement element = contextNode; while (element != null) { if (MyPsiUtil.isNameElement(element)) { return element.getParent(); } else if (MyPsiUtil.isStartTag(contextNode) || MyPsiUtil.isEndTag(contextNode)) { return element.getParent(); } else if (element instanceof XmlAttribute) { return element; } else if (element instanceof XmlComment) { return element; } else if (element instanceof XmlProcessingInstruction) { return element; } else if (element instanceof XmlText) { return element; } element = element.getParent(); } return null; } private static class PathVisitor extends XmlElementVisitor { private final XmlTag context; private final Map<String, String> usedPrefixes = new HashMap<>(); private String uniquePath; private String path; public PathVisitor(XmlTag context) { this.context = context; } @Nullable private String getXPathNameStep(XmlTag tag) { String uri = tag.getNamespace(); if ((uri.length() == 0)) { return tag.getName(); } if (MyPsiUtil.isInDeclaredNamespace(tag, uri, tag.getNamespacePrefix())) { String prefix = tag.getNamespacePrefix(); if (prefix.length() == 0) { final String guessedPrefix = guessPrefix(tag); return guessedPrefix != null ? guessedPrefix + ":" + tag.getLocalName() : "*[name()='" + tag.getName() + "']"; } } return tag.getName(); } @Nullable private String guessPrefix(XmlTag tag) { final String prefix = usedPrefixes.get(tag.getNamespace()); if (prefix != null) { return prefix; } return tryUseUri(tag); } @Nullable private String tryUseUri(XmlTag context) { String segment = chooseSegment(context.getNamespace()); if (segment == null) { return null; } if (segment.length() <= 3 && tryUsePrefix(segment, context)) { return segment; } for (int i = 1; i <= segment.length(); i++) { String prefix = segment.substring(0, i); if (tryUsePrefix(prefix, context)) return prefix; } return null; } private boolean tryUsePrefix(String prefix, XmlTag context) { if (!prefixOk(prefix, context)) return false; usePrefix(prefix, context.getNamespace()); return true; } private boolean prefixOk(String prefix, XmlTag context) { final String namespace = context.getNamespace(); if (!usedPrefixes.containsKey(prefix)) { final String ns = context.getNamespaceByPrefix(prefix); if (ns.length() == 0 || ns.equals(namespace)) { return true; } } return namespace.equals(usedPrefixes.get(prefix)); } private void usePrefix(String prefix, String namespace) { usedPrefixes.put(prefix, namespace); } static private String chooseSegment(String ns) { int off = ns.indexOf('#'); if (off >= 0) { String segment = ns.substring(off + 1).toLowerCase(); if (isValidPrefix(segment)) return segment; } else { off = ns.length(); } for (; ;) { int i = ns.lastIndexOf('/', off - 1); if (i < 0 || (i > 0 && ns.charAt(i - 1) == '/')) break; String segment = ns.substring(i + 1, off).toLowerCase(); if (segmentOk(segment)) return segment; off = i; } off = ns.indexOf(':'); if (off >= 0) { String segment = ns.substring(off + 1).toLowerCase(); if (segmentOk(segment)) return segment; } return null; } private static boolean isValidPrefix(String segment) { return segment.matches("\\p{Alpha}\\p{Alnum}*"); } private static boolean segmentOk(String segment) { return isValidPrefix(segment) && !segment.equals("ns") && !segment.equals("namespace"); } @Override public void visitElement(PsiElement element) { if (element instanceof XmlProcessingInstruction) { visitProcessingInstruction(((XmlProcessingInstruction)element)); } else { super.visitElement(element); } } @Override public void visitXmlAttribute(XmlAttribute attribute) { uniquePath = getUniquePath(attribute); path = getPath(attribute); } public String getPath(XmlAttribute attribute) { StringBuilder result = new StringBuilder(); XmlTag parent = attribute.getParent(); if ((parent != null) && (parent != context)) { result.append(getPath(parent)); result.append("/"); } result.append("@"); String uri = attribute.getNamespace(); String prefix = MyPsiUtil.getAttributePrefix(attribute); if ((uri.length() == 0) || (prefix == null) || (prefix.length() == 0)) { result.append(attribute.getLocalName()); } else { result.append(attribute.getName()); } return result.toString(); } public String getUniquePath(XmlAttribute attribute) { StringBuilder result = new StringBuilder(); XmlTag parent = attribute.getParent(); if ((parent != null) && (parent != context)) { result.append(getUniquePath(parent)); result.append("/"); } result.append("@"); String uri = attribute.getNamespace(); String prefix = MyPsiUtil.getAttributePrefix(attribute); if ((uri.length() == 0) || (prefix == null) || (prefix.length() == 0)) { result.append(attribute.getLocalName()); } else { result.append(attribute.getName()); } return result.toString(); } @Override public void visitXmlTag(XmlTag tag) { uniquePath = getUniquePath(tag); path = getPath(tag); } private String getUniquePath(XmlTag tag) { XmlTag parent = tag.getParentTag(); if (parent == null) { return "/" + getXPathNameStep(tag); } final StringBuilder buffer = new StringBuilder(); if (parent != context) { buffer.append(getUniquePath(parent)); buffer.append("/"); } buffer.append(getXPathNameStep(tag)); return makeUnique(buffer.toString(), tag); } @Nullable public String getPath(XmlTag tag) { if (tag == context) { return "."; } XmlTag parent = tag.getParentTag(); if (parent == null) { return "/" + getXPathNameStep(tag); } else if (parent == context) { return getXPathNameStep(tag); } return getPath(parent) + "/" + getXPathNameStep(tag); } @Override public void visitXmlComment(XmlComment comment) { uniquePath = getUniquePath(comment); path = getPath(comment); } public String getPath(XmlComment comment) { XmlTag parent = PsiTreeUtil.getParentOfType(comment, XmlTag.class); return ((parent != null) && (parent != context)) ? (getPath(parent) + "/comment()") : "comment()"; } public String getUniquePath(XmlComment comment) { XmlTag parent = PsiTreeUtil.getParentOfType(comment, XmlTag.class); return makeUnique(((parent != null) && (parent != context)) ? (getUniquePath(parent) + "/comment()") : "comment()", comment); } @Override public void visitXmlText(XmlText text) { uniquePath = getUniquePath(text); path = getPath(text); } public String getPath(XmlText text) { XmlTag parent = PsiTreeUtil.getParentOfType(text, XmlTag.class); return ((parent != null) && (parent != context)) ? (getPath(parent) + "/text()") : "text()"; } public String getUniquePath(XmlText text) { XmlTag parent = PsiTreeUtil.getParentOfType(text, XmlTag.class); return makeUnique(((parent != null) && (parent != context)) ? (getUniquePath(parent) + "/text()") : "text()", text); } protected void visitProcessingInstruction(XmlProcessingInstruction processingInstruction) { uniquePath = getUniquePath(processingInstruction); path = getPath(processingInstruction); } public String getPath(XmlProcessingInstruction processingInstruction) { XmlTag parent = processingInstruction.getParentTag(); return ((parent != null) && (parent != context)) ? (getPath(parent) + "/processing-instruction()") : "processing-instruction()"; } public String getUniquePath(XmlProcessingInstruction processingInstruction) { XmlTag parent = processingInstruction.getParentTag(); final String target = PsiDocumentNavigator.getProcessingInstructionTarget(processingInstruction); final String s = target != null ? "'" + target + "'" : ""; return makeUnique(((parent != null) && (parent != context)) ? (getUniquePath(parent) + "/processing-instruction(" + s + ")") : "processing-instruction(" + s + ")", processingInstruction); } public String getUniquePath() { return uniquePath; } public String getPath() { return path; } String makeUnique(String uniquePath, XmlElement what) { final XmlFile file = (XmlFile)what.getContainingFile(); assert file != null; try { final XPath xPath = xpathSupport.createXPath(file, uniquePath, Namespace.fromMap(usedPrefixes)); final Object o = xPath.evaluate(file.getDocument()); if (o instanceof List) { //noinspection RawUseOfParameterizedType final List list = (List)o; if (list.size() > 1) { if (what instanceof XmlTag) { final XmlTag tag = (XmlTag)what; final XmlAttribute[] attributes = tag.getAttributes(); if (attributes.length > 0) { for (XmlAttribute attribute : attributes) { final String name = attribute.getName(); final XmlAttributeDescriptor descriptor = attribute.getDescriptor(); if ((attribute.getValue() != null && (descriptor != null && descriptor.hasIdType()) || name.equalsIgnoreCase("id") || name.equalsIgnoreCase("name"))) { final StringBuilder buffer = new StringBuilder(uniquePath); buffer.append("[@"); buffer.append(name); buffer.append("='"); buffer.append(attribute.getValue()); buffer.append("']"); return buffer.toString(); } } } } int i = 1; for (Object o1 : list) { if (o1 == what) { return uniquePath + "[" + i + "]"; } else { i++; } } assert false : "Expression " + uniquePath + " didn't find input element " + what; } } else { assert false : "Unknown return value: " + o; } } catch (JaxenException e) { Logger.getInstance("XPathExpressionGenerator").error(e); } return uniquePath; } } }