blob: efc34ca2958900bd737afd2bdacf989f28036b81 [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* 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 com.intellij.psi.impl.source.resolve.reference.impl.providers;
import com.intellij.codeInsight.daemon.EmptyResolveMessageProvider;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.LocalQuickFixProvider;
import com.intellij.lang.html.HTMLLanguage;
import com.intellij.lang.xhtml.XHTMLLanguage;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.filters.ElementFilter;
import com.intellij.psi.impl.source.xml.XmlEntityRefImpl;
import com.intellij.psi.templateLanguages.TemplateLanguageFileViewProvider;
import com.intellij.psi.xml.*;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ProcessingContext;
import com.intellij.xml.XmlBundle;
import com.intellij.xml.XmlElementDescriptor;
import com.intellij.xml.XmlNSDescriptor;
import com.intellij.xml.util.AddDtdDeclarationFix;
import com.intellij.xml.util.XmlUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* @author Maxim.Mossienko
*/
public class DtdReferencesProvider extends PsiReferenceProvider {
static class ElementReference implements PsiReference, LocalQuickFixProvider, EmptyResolveMessageProvider {
private final XmlElement myElement;
private XmlElement myNameElement;
private final TextRange myRange;
@NonNls private static final String ELEMENT_DECLARATION_NAME = "ELEMENT";
public ElementReference(final XmlElement element, final XmlElement nameElement) {
myElement = element;
myNameElement = nameElement;
final int textOffset = element.getTextRange().getStartOffset();
final int nameTextOffset = nameElement.getTextOffset();
myRange = new TextRange(
nameTextOffset - textOffset,
nameTextOffset + nameElement.getTextLength() - textOffset
);
}
@Override
public PsiElement getElement() {
return myElement;
}
@Override
public TextRange getRangeInElement() {
return myRange;
}
@Override
@Nullable
public PsiElement resolve() {
XmlElementDescriptor descriptor = DtdResolveUtil.resolveElementReference(getCanonicalText(), myElement);
return descriptor == null ? null : descriptor.getDeclaration();
}
@Override
@NotNull
public String getCanonicalText() {
final XmlElement nameElement = myNameElement;
return nameElement != null ? nameElement.getText() : "";
}
@Override
public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
myNameElement = ElementManipulators.getManipulator(myNameElement).handleContentChange(
myNameElement,
new TextRange(0,myNameElement.getTextLength()),
newElementName
);
return null;
}
@Override
public PsiElement bindToElement(@NotNull PsiElement element) throws IncorrectOperationException {
return null;
}
@Override
public boolean isReferenceTo(PsiElement element) {
return myElement.getManager().areElementsEquivalent(element, resolve());
}
@Override
@NotNull
public Object[] getVariants() {
final XmlNSDescriptor rootTagNSDescriptor = DtdResolveUtil.getNsDescriptor(myElement);
return rootTagNSDescriptor != null ?
rootTagNSDescriptor.getRootElementsDescriptors(((XmlFile)getRealFile()).getDocument()):
ArrayUtil.EMPTY_OBJECT_ARRAY;
}
private PsiFile getRealFile() {
PsiFile psiFile = myElement.getContainingFile();
if (psiFile != null) psiFile = psiFile.getOriginalFile();
return psiFile;
}
@Override
public boolean isSoft() {
return true;
}
@Override
public LocalQuickFix[] getQuickFixes() {
if (!canHaveAdequateFix(getElement())) return LocalQuickFix.EMPTY_ARRAY;
return new LocalQuickFix[] {
new AddDtdDeclarationFix(
"xml.dtd.create.dtd.element.intention.name",
ELEMENT_DECLARATION_NAME,
this
)
};
}
@Override
@NotNull
public String getUnresolvedMessagePattern() {
return XmlBundle.message("xml.dtd.unresolved.element.reference", getCanonicalText());
}
}
static class EntityReference implements PsiReference,LocalQuickFixProvider, EmptyResolveMessageProvider {
private final PsiElement myElement;
private final TextRange myRange;
@NonNls private static final String ENTITY_DECLARATION_NAME = "ENTITY";
EntityReference(PsiElement element) {
myElement = element;
if (element instanceof XmlEntityRef) {
final PsiElement child = element.getLastChild();
final int startOffsetInParent = child.getStartOffsetInParent();
myRange = new TextRange(startOffsetInParent + 1, startOffsetInParent + child.getTextLength() - 1);
} else {
myRange = new TextRange(1,myElement.getTextLength()-1);
}
}
@Override
public PsiElement getElement() {
return myElement;
}
@Override
public TextRange getRangeInElement() {
return myRange;
}
@Override
@Nullable
public PsiElement resolve() {
XmlEntityDecl xmlEntityDecl = XmlEntityRefImpl.resolveEntity(
(XmlElement)myElement,
(myElement instanceof XmlEntityRef ? myElement.getLastChild():myElement).getText(),
myElement.getContainingFile()
);
if (xmlEntityDecl != null && !xmlEntityDecl.isPhysical()) {
PsiNamedElement element = XmlUtil.findRealNamedElement(xmlEntityDecl);
if (element != null) xmlEntityDecl = (XmlEntityDecl)element;
}
return xmlEntityDecl;
}
@Override
@NotNull
public String getCanonicalText() {
return myRange.substring(myElement.getText());
}
@Override
public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
final PsiElement elementAt = myElement.findElementAt(myRange.getStartOffset());
return ElementManipulators.getManipulator(elementAt).handleContentChange(elementAt, getRangeInElement(), newElementName);
}
@Override
public PsiElement bindToElement(@NotNull PsiElement element) throws IncorrectOperationException {
return null;
}
@Override
public boolean isReferenceTo(PsiElement element) {
return myElement.getManager().areElementsEquivalent(resolve(), element);
}
@Override
@NotNull
public Object[] getVariants() {
return ArrayUtil.EMPTY_OBJECT_ARRAY;
}
@Override
public boolean isSoft() {
return false;
}
@Override
public LocalQuickFix[] getQuickFixes() {
if (!canHaveAdequateFix(getElement())) return LocalQuickFix.EMPTY_ARRAY;
return new LocalQuickFix[] {
new AddDtdDeclarationFix(
"xml.dtd.create.entity.intention.name",
myElement.getText().charAt(myRange.getStartOffset() - 1) == '%' ?
ENTITY_DECLARATION_NAME + " %":
ENTITY_DECLARATION_NAME,
this
)
};
}
@Override
@NotNull
public String getUnresolvedMessagePattern() {
return XmlBundle.message("xml.dtd.unresolved.entity.reference", getCanonicalText());
}
}
private static boolean canHaveAdequateFix(PsiElement element) {
final PsiFile containingFile = element.getContainingFile();
if (containingFile.getLanguage() == HTMLLanguage.INSTANCE ||
containingFile.getLanguage() == XHTMLLanguage.INSTANCE ||
containingFile.getViewProvider() instanceof TemplateLanguageFileViewProvider
) {
return false;
}
return true;
}
@Override
@NotNull
public PsiReference[] getReferencesByElement(@NotNull final PsiElement element, @NotNull final ProcessingContext context) {
XmlElement nameElement = null;
if (element instanceof XmlDoctype) {
nameElement = ((XmlDoctype)element).getNameElement();
} else if (element instanceof XmlElementDecl) {
nameElement = ((XmlElementDecl)element).getNameElement();
} else if (element instanceof XmlAttlistDecl) {
nameElement = ((XmlAttlistDecl)element).getNameElement();
}
else if (element instanceof XmlElementContentSpec) {
final List<PsiReference> psiRefs = new ArrayList<PsiReference>();
element.accept(new PsiRecursiveElementVisitor() {
@Override
public void visitElement(PsiElement child) {
if (child instanceof XmlToken && ((XmlToken)child).getTokenType() == XmlTokenType.XML_NAME) {
psiRefs.add(new ElementReference((XmlElement)element, (XmlElement)child));
}
super.visitElement(child);
}
});
return psiRefs.toArray(new PsiReference[psiRefs.size()]);
}
if (nameElement != null) {
return new PsiReference[] { new ElementReference((XmlElement)element, nameElement) };
}
if (element instanceof XmlEntityRef ||
(element instanceof XmlToken && ((XmlToken)element).getTokenType() == XmlTokenType.XML_CHAR_ENTITY_REF)) {
return new PsiReference[] { new EntityReference(element) };
}
return PsiReference.EMPTY_ARRAY;
}
public ElementFilter getSystemReferenceFilter() {
return new ElementFilter() {
@Override
public boolean isAcceptable(Object element, PsiElement context) {
final PsiElement parent = context.getParent();
if((parent instanceof XmlEntityDecl &&
!((XmlEntityDecl)parent).isInternalReference()
)
) {
PsiElement prevSibling = context.getPrevSibling();
if (prevSibling instanceof PsiWhiteSpace) {
prevSibling = prevSibling.getPrevSibling();
}
if (prevSibling instanceof XmlToken &&
((XmlToken)prevSibling).getTokenType() == XmlTokenType.XML_DOCTYPE_SYSTEM ||
prevSibling instanceof XmlAttributeValue
) {
return true;
}
}
return false;
}
@Override
public boolean isClassAcceptable(Class hintClass) {
return true;
}
};
}
}