blob: b74834a28162452a02caa79b1af01de5ecb01476 [file] [log] [blame]
/*
* Copyright 2000-2014 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.xml;
import com.intellij.ide.highlighter.DTDFileType;
import com.intellij.openapi.util.Key;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry;
import com.intellij.psi.search.PsiElementProcessor;
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.*;
import com.intellij.util.ArrayUtil;
import com.intellij.xml.Html5SchemaProvider;
import com.intellij.xml.XmlElementDescriptor;
import com.intellij.xml.impl.schema.AnyXmlElementDescriptor;
import com.intellij.xml.util.HtmlUtil;
import com.intellij.xml.util.XmlUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @author mike
*/
public class XmlEntityRefImpl extends XmlElementImpl implements XmlEntityRef {
@NonNls private static final String GT_ENTITY = ">";
@NonNls private static final String QUOT_ENTITY = """;
public XmlEntityRefImpl() {
super(XmlElementType.XML_ENTITY_REF);
}
private static final Key<String> EVALUATION_IN_PROCESS = Key.create("EvalKey");
@Override
public XmlEntityDecl resolve(PsiFile targetFile) {
String text = getText();
if (text.equals(GT_ENTITY) || text.equals(QUOT_ENTITY)) return null;
return resolveEntity(this, text, targetFile);
}
public static XmlEntityDecl resolveEntity(final XmlElement element, final String text, PsiFile targetFile) {
if (targetFile instanceof XmlFile) {
XmlDocument document = ((XmlFile)targetFile).getDocument();
if (document != null && document.getUserData(DISABLE_ENTITY_EXPAND) != null) return null;
}
final String entityName = text.substring(1, text.length() - 1);
final PsiElement targetElement = targetFile != null ? targetFile : element;
CachedValue<XmlEntityDecl> value;
synchronized(PsiLock.LOCK) {
Map<String, CachedValue<XmlEntityDecl>> map = XmlEntityCache.getCachingMap(targetElement);
value = map.get(entityName);
final PsiFile containingFile = element.getContainingFile();
if (value == null) {
final PsiManager manager = element.getManager();
if(manager == null){
return resolveEntity(targetElement, entityName, containingFile).getValue();
}
value = CachedValuesManager.getManager(manager.getProject()).createCachedValue(new CachedValueProvider<XmlEntityDecl>() {
@Override
public Result<XmlEntityDecl> compute() {
return resolveEntity(targetElement, entityName, containingFile);
}
});
map.put(entityName, value);
}
}
return value.getValue();
}
private static final Key<Boolean> DISABLE_ENTITY_EXPAND = Key.create("disable.entity.expand");
private static CachedValueProvider.Result<XmlEntityDecl> resolveEntity(final PsiElement targetElement, final String entityName, PsiFile contextFile) {
if (targetElement.getUserData(EVALUATION_IN_PROCESS) != null) {
return new CachedValueProvider.Result<XmlEntityDecl>(null,targetElement);
}
try {
targetElement.putUserData(EVALUATION_IN_PROCESS, "");
final List<PsiElement> deps = new ArrayList<PsiElement>();
final XmlEntityDecl[] result = {null};
PsiElementProcessor processor = new PsiElementProcessor() {
@Override
public boolean execute(@NotNull PsiElement element) {
if (element instanceof XmlDoctype) {
XmlDoctype xmlDoctype = (XmlDoctype)element;
final String dtdUri = getDtdForEntity(xmlDoctype);
if (dtdUri != null) {
XmlFile file = XmlUtil.getContainingFile(element);
if (file == null) return true;
final XmlFile xmlFile = XmlUtil.findNamespace(file, dtdUri);
if (xmlFile != null) {
if (xmlFile != targetElement) {
deps.add(xmlFile);
if(!XmlUtil.processXmlElements(xmlFile, this,true)) return false;
}
}
}
final XmlMarkupDecl markupDecl = xmlDoctype.getMarkupDecl();
if (markupDecl != null) {
if (!XmlUtil.processXmlElements(markupDecl, this, true)) return false;
}
}
else if (element instanceof XmlEntityDecl) {
XmlEntityDecl entityDecl = (XmlEntityDecl)element;
final String declName = entityDecl.getName();
if (declName.equals(entityName)) {
result[0] = entityDecl;
return false;
}
}
return true;
}
};
FileViewProvider provider = targetElement.getContainingFile().getViewProvider();
deps.add(provider.getPsi(provider.getBaseLanguage()));
boolean notfound = PsiTreeUtil.processElements(targetElement, processor);
if (notfound) {
if (contextFile != targetElement && contextFile != null && contextFile.isValid()) {
notfound = PsiTreeUtil.processElements(contextFile, processor);
}
}
if (notfound && // no dtd ref at all
targetElement instanceof XmlFile &&
deps.size() == 1 &&
((XmlFile)targetElement).getFileType() != DTDFileType.INSTANCE
) {
XmlDocument document = ((XmlFile)targetElement).getDocument();
final XmlTag rootTag = document.getRootTag();
XmlFile descriptorFile = null;
if (HtmlUtil.isHtml5Document(document)) {
descriptorFile = XmlUtil.findXmlFile((XmlFile)targetElement, Html5SchemaProvider.getCharsDtdLocation());
} else if (rootTag != null && document.getUserData(DISABLE_ENTITY_EXPAND) == null) {
final XmlElementDescriptor descriptor = rootTag.getDescriptor();
if (descriptor != null && !(descriptor instanceof AnyXmlElementDescriptor)) {
PsiElement element = descriptor.getDeclaration();
final PsiFile containingFile = element != null ? element.getContainingFile():null;
descriptorFile = containingFile instanceof XmlFile ? (XmlFile)containingFile:null;
}
}
if (descriptorFile != null &&
!descriptorFile.getName().equals(((XmlFile)targetElement).getName() + ".dtd")) {
deps.add(descriptorFile);
XmlUtil.processXmlElements(
descriptorFile,
processor,
true
);
}
}
return new CachedValueProvider.Result<XmlEntityDecl>(result[0], ArrayUtil.toObjectArray(deps));
}
finally {
targetElement.putUserData(EVALUATION_IN_PROCESS, null);
}
}
private static String getDtdForEntity(XmlDoctype xmlDoctype) {
return HtmlUtil.isHtml5Doctype(xmlDoctype) ? Html5SchemaProvider.getCharsDtdLocation() : XmlUtil.getDtdUri(xmlDoctype);
}
@Override
public XmlTag getParentTag() {
final XmlElement parent = (XmlElement)getParent();
if(parent instanceof XmlTag) return (XmlTag)parent;
return null;
}
@Override
public XmlTagChild getNextSiblingInTag() {
PsiElement nextSibling = getNextSibling();
if(nextSibling instanceof XmlTagChild) return (XmlTagChild)nextSibling;
return null;
}
@Override
public XmlTagChild getPrevSiblingInTag() {
final PsiElement prevSibling = getPrevSibling();
if(prevSibling instanceof XmlTagChild) return (XmlTagChild)prevSibling;
return null;
}
@Override
@NotNull
public PsiReference[] getReferences() {
return ReferenceProvidersRegistry.getReferencesFromProviders(this,XmlEntityRef.class);
}
@Override
public void accept(@NotNull PsiElementVisitor visitor) {
if (visitor instanceof XmlElementVisitor) {
((XmlElementVisitor)visitor).visitXmlElement(this);
}
else {
visitor.visitElement(this);
}
}
public static void setNoEntityExpandOutOfDocument(XmlDocument doc, boolean b) {
if (b) doc.putUserData(DISABLE_ENTITY_EXPAND, Boolean.TRUE);
else doc.putUserData(DISABLE_ENTITY_EXPAND, null);
}
}