blob: b9840d656e16c0fbec9801637a3685e570380b76 [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.xml;
import com.intellij.html.impl.RelaxedHtmlFromSchemaElementDescriptor;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.html.HtmlTag;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlDocument;
import com.intellij.psi.xml.XmlElement;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.xml.XmlElementDescriptor;
import com.intellij.xml.XmlElementDescriptorAwareAboutChildren;
import com.intellij.xml.XmlExtension;
import com.intellij.xml.XmlNSDescriptor;
import com.intellij.xml.impl.schema.AnyXmlElementDescriptor;
import com.intellij.xml.impl.schema.XmlElementDescriptorImpl;
import com.intellij.xml.util.HtmlUtil;
import com.intellij.xml.util.XmlUtil;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class TagNameVariantCollector {
public static List<XmlElementDescriptor> getTagDescriptors(final XmlTag element,
final Collection<String> namespaces,
@Nullable List<String> nsInfo) {
XmlElementDescriptor elementDescriptor = null;
String elementNamespace = element.getNamespacePrefix().isEmpty() ? null : element.getNamespace();
final Map<String, XmlElementDescriptor> descriptorsMap = new HashMap<String, XmlElementDescriptor>();
PsiElement context = element.getParent();
PsiElement curElement = element.getParent();
{
while(curElement instanceof XmlTag){
final XmlTag declarationTag = (XmlTag)curElement;
final String namespace = declarationTag.getNamespace();
if(!descriptorsMap.containsKey(namespace)) {
final XmlElementDescriptor descriptor = declarationTag.getDescriptor();
if(descriptor != null) {
descriptorsMap.put(namespace, descriptor);
if(elementDescriptor == null) {
elementDescriptor = descriptor;
if (elementNamespace == null) {
elementNamespace = namespace;
}
}
}
}
curElement = curElement.getContext();
}
}
final Set<XmlNSDescriptor> visited = new HashSet<XmlNSDescriptor>();
final XmlExtension extension = XmlExtension.getExtension(element.getContainingFile());
final ArrayList<XmlElementDescriptor> variants = new ArrayList<XmlElementDescriptor>();
for (final String namespace: namespaces) {
final int initialSize = variants.size();
processVariantsInNamespace(namespace, element, variants, elementDescriptor, elementNamespace, descriptorsMap, visited,
context instanceof XmlTag ? (XmlTag)context : element, extension);
if (nsInfo != null) {
for (int i = initialSize; i < variants.size(); i++) {
XmlElementDescriptor descriptor = variants.get(i);
nsInfo.add(descriptor instanceof XmlElementDescriptorImpl && !(descriptor instanceof RelaxedHtmlFromSchemaElementDescriptor)
? ((XmlElementDescriptorImpl)descriptor).getNamespaceByContext(element)
: namespace);
}
}
}
final boolean hasPrefix = StringUtil.isNotEmpty(element.getNamespacePrefix());
return ContainerUtil.filter(variants, new Condition<XmlElementDescriptor>() {
@Override
public boolean value(XmlElementDescriptor descriptor) {
if (descriptor instanceof AnyXmlElementDescriptor) {
return false;
}
else if (hasPrefix && descriptor instanceof XmlElementDescriptorImpl &&
!namespaces.contains(((XmlElementDescriptorImpl)descriptor).getNamespace())) {
return false;
}
return true;
}
});
}
private static void processVariantsInNamespace(final String namespace,
final XmlTag element,
final List<XmlElementDescriptor> variants,
final XmlElementDescriptor elementDescriptor,
final String elementNamespace,
final Map<String, XmlElementDescriptor> descriptorsMap,
final Set<XmlNSDescriptor> visited,
XmlTag parent,
final XmlExtension extension) {
if(descriptorsMap.containsKey(namespace)){
final XmlElementDescriptor descriptor = descriptorsMap.get(namespace);
if(isAcceptableNs(element, elementDescriptor, elementNamespace, namespace)){
for(XmlElementDescriptor containedDescriptor: descriptor.getElementsDescriptors(parent)) {
if (containedDescriptor != null) variants.add(containedDescriptor);
}
}
if (element instanceof HtmlTag) {
HtmlUtil.addHtmlSpecificCompletions(descriptor, element, variants);
}
visited.add(descriptor.getNSDescriptor());
}
else{
// Don't use default namespace in case there are other namespaces in scope
// If there are tags from default namespace they will be handled via
// their element descriptors (prev if section)
if (namespace == null) return;
if(namespace.isEmpty() && !visited.isEmpty()) return;
XmlNSDescriptor nsDescriptor = getDescriptor(element, namespace, true, extension);
if (nsDescriptor == null) {
if(!descriptorsMap.isEmpty()) return;
nsDescriptor = getDescriptor(element, namespace, false, extension);
}
if(nsDescriptor != null && !visited.contains(nsDescriptor) &&
isAcceptableNs(element, elementDescriptor, elementNamespace, namespace)
){
visited.add(nsDescriptor);
final XmlElementDescriptor[] rootElementsDescriptors =
nsDescriptor.getRootElementsDescriptors(PsiTreeUtil.getParentOfType(element, XmlDocument.class));
final XmlTag parentTag = extension.getParentTagForNamespace(element, nsDescriptor);
final XmlElementDescriptor parentDescriptor;
if (parentTag == element.getParentTag()) {
parentDescriptor = elementDescriptor;
}
else {
assert parentTag != null;
parentDescriptor = parentTag.getDescriptor();
}
for(XmlElementDescriptor candidateDescriptor: rootElementsDescriptors) {
if (candidateDescriptor != null &&
couldContainDescriptor(parentTag, parentDescriptor, candidateDescriptor, namespace, false)) {
variants.add(candidateDescriptor);
}
}
}
}
}
private static XmlNSDescriptor getDescriptor(final XmlTag element, final String namespace, final boolean strict,
final XmlExtension extension) {
return extension.getNSDescriptor(element, namespace, strict);
}
static boolean couldContainDescriptor(final XmlTag parentTag,
final XmlElementDescriptor parentDescriptor,
final XmlElementDescriptor childDescriptor,
String childNamespace, boolean strict) {
if (XmlUtil.nsFromTemplateFramework(childNamespace)) return true;
if (parentTag == null) return true;
if (parentDescriptor == null) return false;
final XmlTag childTag = parentTag.createChildTag(childDescriptor.getName(), childNamespace, null, false);
childTag.putUserData(XmlElement.INCLUDING_ELEMENT, parentTag);
XmlElementDescriptor descriptor = parentDescriptor.getElementDescriptor(childTag, parentTag);
return descriptor != null && (!strict || !(descriptor instanceof AnyXmlElementDescriptor));
}
private static boolean isAcceptableNs(final XmlTag element, final XmlElementDescriptor elementDescriptor,
final String elementNamespace,
final String namespace) {
return !(elementDescriptor instanceof XmlElementDescriptorAwareAboutChildren) ||
elementNamespace == null ||
elementNamespace.equals(namespace) ||
((XmlElementDescriptorAwareAboutChildren)elementDescriptor).allowElementsFromNamespace(namespace, element.getParentTag());
}
public static boolean couldContain(XmlTag parent, XmlTag child) {
return couldContainDescriptor(parent, parent.getDescriptor(), child.getDescriptor(), child.getNamespace(), true);
}
}