blob: 329698a0b0368b0ae65f9c7aed74d997fda30fc1 [file] [log] [blame]
package org.intellij.lang.xpath.xslt.impl.references;
import com.intellij.javaee.ExternalResourceManager;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.PsiReferenceProvider;
import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReferenceSet;
import com.intellij.psi.util.CachedValue;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlDocument;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ProcessingContext;
import com.intellij.util.SmartList;
import com.intellij.util.io.URLUtil;
import org.intellij.lang.xpath.psi.impl.ResolveUtil;
import org.intellij.lang.xpath.xslt.XsltSupport;
import org.intellij.lang.xpath.xslt.impl.XsltIncludeIndex;
import org.intellij.lang.xpath.xslt.psi.*;
import org.intellij.lang.xpath.xslt.util.*;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class XsltReferenceProvider extends PsiReferenceProvider {
private static final Key<CachedValue<PsiReference[]>> CACHED_XSLT_REFS = Key.create("CACHED_XSLT_REFS");
private final XsltElementFactory myXsltElementFactory = XsltElementFactory.getInstance();
private static final Pattern ELEMENT_PATTERN = Pattern.compile("(?:(\\w+):)?(?:\\w+|\\*)");
private static final Pattern PREFIX_PATTERN = Pattern.compile("(?:^|\\s)(\\w+)");
public XsltReferenceProvider() {
}
@NotNull
public PsiReference[] getReferencesByElement(@NotNull PsiElement e, @NotNull ProcessingContext context) {
final PsiElement element = e.getParent();
if (element instanceof XmlAttribute) {
final XmlAttribute attribute = (XmlAttribute)element;
CachedValue<PsiReference[]> cachedValue = attribute.getUserData(CACHED_XSLT_REFS);
if (cachedValue == null) {
cachedValue = CachedValuesManager.getManager(element.getProject()).createCachedValue(new ReferenceProvider(attribute), false);
attribute.putUserData(CACHED_XSLT_REFS, cachedValue);
}
final PsiReference[] value = cachedValue.getValue();
assert value != null;
return value;
} else {
return PsiReference.EMPTY_ARRAY;
}
}
private class ReferenceProvider implements CachedValueProvider<PsiReference[]> {
private final XmlAttribute myAttribute;
ReferenceProvider(XmlAttribute attribute) {
myAttribute = attribute;
}
public Result<PsiReference[]> compute() {
final PsiReference[] referencesImpl = getReferencesImpl(myAttribute);
final Object[] refs = new PsiElement[referencesImpl.length];
for (int i = 0; i < refs.length; i++) {
refs[i] = referencesImpl[i].getElement();
}
return new Result<PsiReference[]>(referencesImpl, ArrayUtil.append(refs, myAttribute.getValueElement()));
}
private PsiReference[] getReferencesImpl(final XmlAttribute attribute) {
final PsiReference[] psiReferences;
final XmlTag tag = attribute.getParent();
if (XsltSupport.isTemplateCallName(attribute)) {
psiReferences = createReferencesWithPrefix(attribute, new TemplateReference(attribute));
} else if (XsltSupport.isTemplateCallParamName(attribute)) {
final String paramName = attribute.getValue();
final XmlTag templateCall = PsiTreeUtil.getParentOfType(tag, XmlTag.class);
if (templateCall != null) {
if (XsltSupport.isTemplateCall(templateCall)) {
final XsltCallTemplate call = myXsltElementFactory.wrapElement(templateCall, XsltCallTemplate.class);
final ResolveUtil.Matcher matcher = new MyParamMatcher(paramName, call);
psiReferences = new PsiReference[]{ new AttributeReference(attribute, matcher, true) };
} else if (XsltSupport.isApplyTemplates(templateCall)) {
final XsltApplyTemplates call = myXsltElementFactory.wrapElement(templateCall, XsltApplyTemplates.class);
final ResolveUtil.Matcher matcher = new MyParamMatcher2(paramName, call);
psiReferences = new PsiReference[]{ new ParamReference(attribute, matcher) };
} else {
psiReferences = PsiReference.EMPTY_ARRAY;
}
} else {
psiReferences = PsiReference.EMPTY_ARRAY;
}
} else if (XsltSupport.isParam(attribute) && isInsideUnnamedTemplate(tag)) {
final XsltParameter myParam = myXsltElementFactory.wrapElement(tag, XsltParameter.class);
psiReferences = new PsiReference[]{ new MySelfReference(attribute, myParam) };
} else if (XsltSupport.isVariableOrParamName(attribute) || XsltSupport.isTemplateName(attribute)) {
final XsltElement myElement = myXsltElementFactory.wrapElement(tag, XsltElement.class);
psiReferences = createReferencesWithPrefix(attribute, SelfReference.create(attribute, myElement));
} else if (XsltSupport.isFunctionName(attribute)) {
final XsltFunction myElement = myXsltElementFactory.wrapElement(tag, XsltFunction.class);
psiReferences = createReferencesWithPrefix(attribute, SelfReference.create(attribute, myElement));
} else if (XsltSupport.isIncludeOrImportHref(attribute)) {
final String href = attribute.getValue();
final String resourceLocation = ExternalResourceManager.getInstance().getResourceLocation(href, attribute.getProject());
//noinspection StringEquality
if (href == resourceLocation) {
// not a configured external resource
if (!URLUtil.containsScheme(href)) {
// a local file reference
final FileReferenceSet filereferenceset = new FileReferenceSet(
href,
attribute.getValueElement(), 1, XsltReferenceProvider.this, true);
psiReferences = filereferenceset.getAllReferences();
} else {
// external, but unknown resource
psiReferences = new PsiReference[]{ new ExternalResourceReference(attribute) };
}
} else {
// external, known resource
psiReferences = new PsiReference[]{ new ExternalResourceReference(attribute) };
}
} else if (XsltSupport.isMode(attribute)) {
psiReferences = ModeReference.create(attribute, XsltSupport.isTemplate(tag, false));
} else if ((attribute.getLocalName().equals("extension-element-prefixes") ||
attribute.getLocalName().equals("exclude-result-prefixes")) && XsltSupport.isXsltRootTag(tag)) {
psiReferences = createPrefixReferences(attribute, PREFIX_PATTERN);
} else if (attribute.getLocalName().equals("stylesheet-prefix") && tag.getLocalName().equals("namespace-alias")) {
psiReferences = createPrefixReferences(attribute, PREFIX_PATTERN);
} else if ("elements".equals(attribute.getLocalName())) {
if (("strip-space".equals(tag.getLocalName()) || "preserve-space".equals(tag.getLocalName()))) {
psiReferences = createPrefixReferences(attribute, ELEMENT_PATTERN);
} else {
psiReferences = PsiReference.EMPTY_ARRAY;
}
} else {
psiReferences = PsiReference.EMPTY_ARRAY;
}
return psiReferences;
}
private PsiReference[] createReferencesWithPrefix(XmlAttribute attribute, PsiReference reference) {
if (attribute.getValue().contains(":")) {
return new PsiReference[]{ new PrefixReference(attribute), reference };
} else {
return new PsiReference[]{ reference };
}
}
private class MySelfReference extends SelfReference {
private final XsltParameter myParam;
private final XmlTag myTag;
public MySelfReference(XmlAttribute attribute, XsltParameter param) {
super(attribute, param);
myParam = param;
myTag = param.getTag();
}
public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
if (!newElementName.equals(myParam.getName())) {
myParam.setName(newElementName);
}
final XmlAttribute attribute = myParam.getNameAttribute();
assert attribute != null;
//noinspection ConstantConditions
return attribute.getValueElement();
}
public boolean isReferenceTo(PsiElement element) {
// self-reference is only a trick to enable rename/find usages etc. but it shouldn't actually
// refer to itself because this would list the element to be renamed/searched for twice
assert !super.isReferenceTo(element);
if (element == myParam) return false;
if (!(element instanceof XsltParameter)) return false;
final XsltParameter param = ((XsltParameter)element);
final String name = param.getName();
if (name == null || !name.equals(myParam.getName())) return false;
final XsltTemplate template = XsltCodeInsightUtil.getTemplate(myTag, false);
final XsltTemplate myTemplate = XsltCodeInsightUtil.getTemplate(param.getTag(), false);
if (template == myTemplate) return true;
if (template == null || myTemplate == null) return false;
if (!Comparing.equal(template.getMode(), myTemplate.getMode())) {
return false;
}
final XmlFile xmlFile = (XmlFile)element.getContainingFile();
final XmlFile myFile = (XmlFile)myParam.getContainingFile();
if (myFile == xmlFile) return true;
return XsltIncludeIndex.isReachableFrom(myFile, xmlFile);
}
}
}
private static PsiReference[] createPrefixReferences(XmlAttribute attribute, Pattern pattern) {
final Matcher matcher = pattern.matcher(attribute.getValue());
if (matcher.find()) {
final List<PsiReference> refs = new SmartList<PsiReference>();
do {
final int start = matcher.start(1);
if (start >= 0) {
refs.add(new PrefixReference(attribute, TextRange.create(start, matcher.end(1))));
}
}
while (matcher.find());
return refs.toArray(new PsiReference[refs.size()]);
}
return PsiReference.EMPTY_ARRAY;
}
private static boolean isInsideUnnamedTemplate(XmlTag tag) {
final XmlTag t = XsltCodeInsightUtil.getTemplateTag(tag, false, false);
return t != null && t.getAttribute("name", null) == null;
}
static class MyParamMatcher extends NamedTemplateMatcher {
private final XsltCallTemplate myCall;
private final String myParamName;
private String[] myExcludedNames = ArrayUtil.EMPTY_STRING_ARRAY;
MyParamMatcher(String paramName, XsltCallTemplate call) {
super(XsltCodeInsightUtil.getDocument(call), call.getTemplateName());
myCall = call;
myParamName = paramName;
}
private MyParamMatcher(String paramName, XsltCallTemplate call, String[] excludedNames) {
super(getDocument(call), call.getTemplateName());
myCall = call;
myParamName = paramName;
myExcludedNames = excludedNames;
}
private static XmlDocument getDocument(XsltCallTemplate call) {
final XsltTemplate template = call.getTemplate();
return XsltCodeInsightUtil.getDocument(template != null ? template : call);
}
@Override
protected ResolveUtil.Matcher changeDocument(XmlDocument document) {
return new MyParamMatcher(myParamName, myCall, myExcludedNames);
}
@Override
protected Result matchImpl(XmlTag element) {
if (matches(element)) {
return Result.create(new ParamMatcher(element, myExcludedNames, myParamName));
}
return null;
}
@Override
public ResolveUtil.Matcher variantMatcher() {
final PsiElement[] suppliedArgs = ResolveUtil.collect(new ArgumentMatcher(myCall));
final String[] excludedNames = new String[suppliedArgs.length];
for (int i = 0; i < suppliedArgs.length; i++) {
excludedNames[i] = ((XmlTag)suppliedArgs[i]).getAttributeValue("name");
}
return new MyParamMatcher(null, myCall, excludedNames);
}
}
static class MyParamMatcher2 extends MatchTemplateMatcher {
private final String myParamName;
private final XsltApplyTemplates myCall;
private String[] myExcludedNames = ArrayUtil.EMPTY_STRING_ARRAY;
MyParamMatcher2(String paramName, XsltApplyTemplates call) {
super(XsltCodeInsightUtil.getDocument(call), call.getMode());
myParamName = paramName;
myCall = call;
}
private MyParamMatcher2(String paramName, XsltApplyTemplates call, String[] excludedNames) {
this(paramName, call);
myExcludedNames = excludedNames;
}
@Override
protected Result matchImpl(XmlTag element) {
if (matches(element)) {
return Result.create(new ParamMatcher(element, myExcludedNames, myParamName));
}
return null;
}
@Override
protected ResolveUtil.Matcher changeDocument(XmlDocument document) {
return new MyParamMatcher2(myParamName, myCall);
}
@Override
public ResolveUtil.Matcher variantMatcher() {
final PsiElement[] suppliedArgs = ResolveUtil.collect(new ArgumentMatcher(myCall));
final String[] excludedNames = new String[suppliedArgs.length];
for (int i = 0; i < suppliedArgs.length; i++) {
excludedNames[i] = ((XmlTag)suppliedArgs[i]).getAttributeValue("name");
}
return new MyParamMatcher2(null, myCall, excludedNames);
}
}
}