blob: 7bf9f02710b37ce8d88020b97d042f3f7ed17320 [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.xml.impl.schema;
import com.intellij.codeInsight.daemon.Validator;
import com.intellij.psi.ElementManipulators;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiReference;
import com.intellij.psi.meta.PsiWritableMetaData;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.*;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.xml.*;
import com.intellij.xml.util.XmlEnumeratedValueReference;
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.Arrays;
import java.util.List;
/**
* @author Mike
*/
public class XmlElementDescriptorImpl extends XsdEnumerationDescriptor<XmlTag>
implements XmlElementDescriptor, PsiWritableMetaData, Validator<XmlTag>,
XmlElementDescriptorAwareAboutChildren {
protected XmlTag myDescriptorTag;
protected volatile XmlNSDescriptor NSDescriptor;
private volatile @Nullable Validator<XmlTag> myValidator;
@NonNls
public static final String QUALIFIED_ATTR_VALUE = "qualified";
@NonNls
public static final String NONQUALIFIED_ATTR_VALUE = "unqualified";
@NonNls
private static final String ELEMENT_FORM_DEFAULT = "elementFormDefault";
public XmlElementDescriptorImpl(@Nullable XmlTag descriptorTag) {
myDescriptorTag = descriptorTag;
}
public XmlElementDescriptorImpl() {}
@Override
public XmlTag getDeclaration(){
return myDescriptorTag;
}
@Override
public String getName(PsiElement context){
String value = myDescriptorTag.getAttributeValue("name");
if(context instanceof XmlElement){
final String namespace = getNamespaceByContext(context);
final XmlTag tag = PsiTreeUtil.getParentOfType(context, XmlTag.class, false);
if(tag != null){
final String namespacePrefix = tag.getPrefixByNamespace(namespace);
if (namespacePrefix != null && namespacePrefix.length() > 0) {
final XmlTag rootTag = ((XmlFile)myDescriptorTag.getContainingFile()).getRootTag();
String elementFormDefault;
if (rootTag != null &&
( NONQUALIFIED_ATTR_VALUE.equals(elementFormDefault = rootTag.getAttributeValue(ELEMENT_FORM_DEFAULT)) || elementFormDefault == null /*unqualified is default*/) &&
tag.getNamespaceByPrefix("").isEmpty()
&& myDescriptorTag.getParentTag() != rootTag
) {
value = XmlUtil.findLocalNameByQualifiedName(value);
} else {
value = namespacePrefix + ":" + XmlUtil.findLocalNameByQualifiedName(value);
}
}
}
}
return value;
}
/** getter for _local_ name */
@Override
public String getName() {
return XmlUtil.findLocalNameByQualifiedName(getName(null));
}
public String getNamespaceByContext(PsiElement context){
//while(context != null){
// if(context instanceof XmlTag){
// final XmlTag contextTag = ((XmlTag)context);
// final XmlNSDescriptorImpl schemaDescriptor = XmlUtil.findXmlNSDescriptorByType(contextTag);
// if (schemaDescriptor != null) {
// return schemaDescriptor.getDefaultNamespace();
// }
// }
// context = context.getContext();
//}
return getNamespace();
}
public String getNamespace(){
String name = getName();
if (name == null) return XmlUtil.EMPTY_URI;
final XmlNSDescriptorImpl xmlNSDescriptor = (XmlNSDescriptorImpl)getNSDescriptor();
if (xmlNSDescriptor == null || myDescriptorTag == null) return XmlUtil.EMPTY_URI;
final String namespacePrefix = XmlUtil.findPrefixByQualifiedName(name);
return namespacePrefix.isEmpty() ?
xmlNSDescriptor.getDefaultNamespace() :
myDescriptorTag.getNamespaceByPrefix(namespacePrefix);
}
@Override
public void init(PsiElement element){
if (myDescriptorTag!=element && myDescriptorTag!=null) {
NSDescriptor = null;
}
myDescriptorTag = (XmlTag) element;
}
@Override
public Object[] getDependences(){
return new Object[]{myDescriptorTag};
}
private XmlNSDescriptor getNSDescriptor(XmlElement context) {
XmlNSDescriptor nsDescriptor = getNSDescriptor();
if (context instanceof XmlTag && nsDescriptor instanceof XmlNSDescriptorImpl) {
final String defaultNamespace = ((XmlNSDescriptorImpl)nsDescriptor).getDefaultNamespace();
if (XmlUtil.XML_SCHEMA_URI.equals(defaultNamespace)) return nsDescriptor; // do not check for overriden for efficiency
final XmlTag tag = (XmlTag)context;
final String tagNs = tag.getNamespace();
if (tagNs.equals(defaultNamespace)) {
XmlNSDescriptor previousDescriptor = nsDescriptor;
nsDescriptor = tag.getNSDescriptor(tagNs, true);
if (nsDescriptor == null) nsDescriptor = previousDescriptor;
}
}
return nsDescriptor;
}
@Override
public XmlNSDescriptor getNSDescriptor() {
XmlNSDescriptor nsDescriptor = NSDescriptor;
if (nsDescriptor == null || !NSDescriptor.getDeclaration().isValid()) {
final XmlFile file = XmlUtil.getContainingFile(getDeclaration());
if(file == null) return null;
final XmlDocument document = file.getDocument();
if(document == null) return null;
NSDescriptor = nsDescriptor = (XmlNSDescriptor)document.getMetaData();
}
return nsDescriptor;
}
@Override
public XmlElementsGroup getTopGroup() {
TypeDescriptor type = getType();
return type instanceof ComplexTypeDescriptor ? ((ComplexTypeDescriptor)type).getTopGroup() : null;
}
@Nullable
public TypeDescriptor getType() {
return getType(null);
}
@Nullable
public TypeDescriptor getType(XmlElement context) {
final XmlNSDescriptor nsDescriptor = getNSDescriptor(context);
if (!(nsDescriptor instanceof XmlNSTypeDescriptorProvider)) return null;
TypeDescriptor type = ((XmlNSTypeDescriptorProvider) nsDescriptor).getTypeDescriptor(myDescriptorTag);
if (type == null) {
String substAttr = myDescriptorTag.getAttributeValue("substitutionGroup");
if (substAttr != null) {
final String namespacePrefix = XmlUtil.findPrefixByQualifiedName(substAttr);
final String namespace = namespacePrefix.isEmpty() ?
((XmlNSDescriptorImpl)getNSDescriptor()).getDefaultNamespace() :
myDescriptorTag.getNamespaceByPrefix(namespacePrefix);
final String local = XmlUtil.findLocalNameByQualifiedName(substAttr);
final XmlElementDescriptorImpl originalElement = (XmlElementDescriptorImpl)((XmlNSDescriptorImpl)getNSDescriptor()).getElementDescriptor(local, namespace);
if (originalElement != null) {
type = originalElement.getType(context);
}
}
}
return type;
}
@Override
public XmlElementDescriptor[] getElementsDescriptors(XmlTag context) {
if (context != null) {
final XmlElementDescriptor parentDescriptorByType = XmlUtil.findXmlDescriptorByType(context);
if (parentDescriptorByType != null && !parentDescriptorByType.equals(this)) {
return parentDescriptorByType.getElementsDescriptors(context);
}
}
XmlElementDescriptor[] elementsDescriptors = getElementsDescriptorsImpl(context);
final TypeDescriptor type = getType(context);
if (type instanceof ComplexTypeDescriptor) {
final ComplexTypeDescriptor descriptor = (ComplexTypeDescriptor)type;
String contextNs;
PsiFile containingFile = context != null ? context.getContainingFile():null;
if (context != null && !containingFile.isPhysical()) {
containingFile = containingFile.getOriginalFile();
//context = context.getParentTag();
}
if (context != null &&
( descriptor.canContainTag(context.getLocalName(), contextNs = context.getNamespace(), context ) &&
(!contextNs.equals(getNamespace()) || descriptor.hasAnyInContentModel())
) ) {
final XmlNSDescriptor nsDescriptor = getNSDescriptor();
if (nsDescriptor != null) {
elementsDescriptors = ArrayUtil.mergeArrays(
elementsDescriptors,
nsDescriptor.getRootElementsDescriptors(((XmlFile)containingFile).getDocument())
);
}
}
}
return elementsDescriptors;
}
private XmlElementDescriptor[] getElementsDescriptorsImpl(XmlElement context) {
TypeDescriptor type = getType(context);
if (type instanceof ComplexTypeDescriptor) {
ComplexTypeDescriptor typeDescriptor = (ComplexTypeDescriptor)type;
XmlElementDescriptor[] elements = typeDescriptor.getElements(context);
if (context instanceof XmlTag && elements.length > 0) {
String[] namespaces = ((XmlTag)context).knownNamespaces();
if (namespaces.length > 1) {
List<XmlElementDescriptor> result = new ArrayList<XmlElementDescriptor>(Arrays.asList(elements));
for (String namespace : namespaces) {
if (namespace.equals(typeDescriptor.getNsDescriptor().getDefaultNamespace())) {
continue;
}
XmlNSDescriptor descriptor = ((XmlTag)context).getNSDescriptor(namespace, false);
if (descriptor instanceof XmlNSDescriptorImpl && ((XmlNSDescriptorImpl)descriptor).hasSubstitutions()) {
for (XmlElementDescriptor element : elements) {
String name = XmlUtil.getLocalName(element.getName(context)).toString();
String s = ((XmlNSDescriptorImpl)element.getNSDescriptor()).getDefaultNamespace();
XmlElementDescriptor[] substitutes = ((XmlNSDescriptorImpl)descriptor).getSubstitutes(name, s);
result.addAll(Arrays.asList(substitutes));
}
}
}
return result.toArray(new XmlElementDescriptor[result.size()]);
}
}
return elements;
}
return EMPTY_ARRAY;
}
@Override
public XmlAttributeDescriptor[] getAttributesDescriptors(final XmlTag context) {
TypeDescriptor type = getType(context);
if (type instanceof ComplexTypeDescriptor) {
ComplexTypeDescriptor typeDescriptor = (ComplexTypeDescriptor)type;
XmlAttributeDescriptor[] attributeDescriptors = typeDescriptor.getAttributes(context);
if (context != null) {
final String contextNs = context.getNamespace();
boolean seenXmlNs = false;
for(String ns:context.knownNamespaces()) {
if (!contextNs.equals(ns) && ns.length() > 0) {
seenXmlNs |= XmlUtil.XML_NAMESPACE_URI.equals(ns);
attributeDescriptors = updateAttributeDescriptorsFromAny(context, typeDescriptor, attributeDescriptors, ns);
}
}
if (!seenXmlNs) {
attributeDescriptors = updateAttributeDescriptorsFromAny(context, typeDescriptor, attributeDescriptors, XmlUtil.XML_NAMESPACE_URI);
}
}
return attributeDescriptors;
}
return XmlAttributeDescriptor.EMPTY;
}
/** <xsd:anyAttribute> directive processed here */
private static XmlAttributeDescriptor[] updateAttributeDescriptorsFromAny(final XmlTag context,
final ComplexTypeDescriptor typeDescriptor,
XmlAttributeDescriptor[] attributeDescriptors,
final String ns) {
if (typeDescriptor.canContainAttribute(ns, null) != ComplexTypeDescriptor.CanContainAttributeType.CanNotContain) {
// anyAttribute found
final XmlNSDescriptor descriptor = context.getNSDescriptor(ns, true);
if (descriptor instanceof XmlNSDescriptorImpl) {
XmlAttributeDescriptor[] rootDescriptors = ((XmlNSDescriptorImpl)descriptor).getRootAttributeDescriptors(context);
attributeDescriptors = ArrayUtil.mergeArrays(attributeDescriptors, rootDescriptors);
}
}
return attributeDescriptors;
}
@Override
public XmlAttributeDescriptor getAttributeDescriptor(String attributeName, final XmlTag context){
return getAttributeDescriptorImpl(attributeName,context);
}
@Nullable
private XmlAttributeDescriptor getAttributeDescriptorImpl(final String attributeName, XmlTag context) {
final String localName = XmlUtil.findLocalNameByQualifiedName(attributeName);
final String namespacePrefix = XmlUtil.findPrefixByQualifiedName(attributeName);
final XmlNSDescriptorImpl xmlNSDescriptor = (XmlNSDescriptorImpl)getNSDescriptor();
final String namespace = namespacePrefix.isEmpty() ?
((xmlNSDescriptor != null)?xmlNSDescriptor.getDefaultNamespace():"") :
context.getNamespaceByPrefix(namespacePrefix);
XmlAttributeDescriptor attribute = getAttribute(localName, namespace, context, attributeName);
if (attribute instanceof AnyXmlAttributeDescriptor) {
final ComplexTypeDescriptor.CanContainAttributeType containAttributeType =
((AnyXmlAttributeDescriptor)attribute).getCanContainAttributeType();
if (containAttributeType != ComplexTypeDescriptor.CanContainAttributeType.CanContainAny && !namespace.isEmpty()) {
final XmlNSDescriptor candidateNSDescriptor = context.getNSDescriptor(namespace, true);
if (candidateNSDescriptor instanceof XmlNSDescriptorImpl) {
final XmlNSDescriptorImpl nsDescriptor = (XmlNSDescriptorImpl)candidateNSDescriptor;
final XmlAttributeDescriptor xmlAttributeDescriptor = nsDescriptor.getAttribute(localName, namespace, context);
if (xmlAttributeDescriptor != null) return xmlAttributeDescriptor;
else {
if (containAttributeType == ComplexTypeDescriptor.CanContainAttributeType.CanContainButDoNotSkip) {
attribute = null;
}
}
}
}
}
return attribute;
}
@Override
public XmlAttributeDescriptor getAttributeDescriptor(XmlAttribute attribute){
return getAttributeDescriptorImpl(attribute.getName(),attribute.getParent());
}
@Nullable
private XmlAttributeDescriptor getAttribute(String attributeName, String namespace, XmlTag context, String qName) {
XmlAttributeDescriptor[] descriptors = getAttributesDescriptors(context);
for (XmlAttributeDescriptor descriptor : descriptors) {
if (descriptor.getName().equals(attributeName) &&
descriptor.getName(context).equals(qName)
) {
return descriptor;
}
}
TypeDescriptor type = getType(context);
if (type instanceof ComplexTypeDescriptor) {
ComplexTypeDescriptor descriptor = (ComplexTypeDescriptor)type;
final ComplexTypeDescriptor.CanContainAttributeType containAttributeType = descriptor.canContainAttribute(namespace, qName);
if (containAttributeType != ComplexTypeDescriptor.CanContainAttributeType.CanNotContain) {
return new AnyXmlAttributeDescriptor(attributeName, containAttributeType);
}
}
return null;
}
@Override
public int getContentType() {
TypeDescriptor type = getType();
if (type instanceof ComplexTypeDescriptor) {
return ((ComplexTypeDescriptor)type).getContentType();
}
return CONTENT_TYPE_MIXED;
}
@Nullable
public XmlElementDescriptor getElementDescriptor(final String name) {
final String localName = XmlUtil.findLocalNameByQualifiedName(name);
final String namespacePrefix = XmlUtil.findPrefixByQualifiedName(name);
final String namespace = namespacePrefix.isEmpty() ?
((XmlNSDescriptorImpl)getNSDescriptor()).getDefaultNamespace() :
myDescriptorTag.getNamespaceByPrefix(namespacePrefix);
return getElementDescriptor(localName, namespace, null, name);
}
@Nullable
protected XmlElementDescriptor getElementDescriptor(final String localName, final String namespace, XmlElement context, String fullName) {
XmlElementDescriptor[] elements = getElementsDescriptorsImpl(context);
for (XmlElementDescriptor element1 : elements) {
final XmlElementDescriptorImpl element = (XmlElementDescriptorImpl)element1;
final String namespaceByContext = element.getNamespaceByContext(context);
if (element.getName().equals(localName)) {
if (namespace == null ||
namespace.equals(namespaceByContext) ||
namespaceByContext.equals(XmlUtil.EMPTY_URI) ||
element.getName(context).equals(fullName) || (namespace.length() == 0) &&
element.getDefaultName().equals(fullName)
) {
return element;
}
else {
final XmlNSDescriptor descriptor = context instanceof XmlTag? ((XmlTag)context).getNSDescriptor(namespace, true) : null;
// schema's targetNamespace could be different from file systemId used as NS
if (descriptor instanceof XmlNSDescriptorImpl) {
if (((XmlNSDescriptorImpl)descriptor).getDefaultNamespace().equals(namespaceByContext)) {
return element;
}
else {
((XmlNSDescriptorImpl)descriptor).getSubstitutes(localName, namespace);
}
}
}
}
}
TypeDescriptor type = getType(context);
if (type instanceof ComplexTypeDescriptor) {
ComplexTypeDescriptor descriptor = (ComplexTypeDescriptor)type;
if (descriptor.canContainTag(localName, namespace, context)) {
return new AnyXmlElementDescriptor(this, getNSDescriptor());
}
}
return null;
}
@Override
public XmlElementDescriptor getElementDescriptor(XmlTag element, XmlTag contextTag){
final XmlElement context = (XmlElement)element.getParent();
XmlElementDescriptor elementDescriptor = getElementDescriptor(
element.getLocalName(),
element.getNamespace(), context,
element.getName()
);
if(elementDescriptor == null || element.getAttributeValue("xsi:type") != null){
final XmlElementDescriptor xmlDescriptorByType = XmlUtil.findXmlDescriptorByType(element);
if (xmlDescriptorByType != null) elementDescriptor = xmlDescriptorByType;
else if (context instanceof XmlTag && ((XmlTag)context).getAttributeValue("xsi:type") != null && askParentDescriptorViaXsi()) {
final XmlElementDescriptor parentXmlDescriptorByType = XmlUtil.findXmlDescriptorByType(((XmlTag)context));
if (parentXmlDescriptorByType != null) {
elementDescriptor = parentXmlDescriptorByType.getElementDescriptor(element, contextTag);
}
}
}
return elementDescriptor;
}
protected boolean askParentDescriptorViaXsi() {
return true;
}
@Override
public String getQualifiedName() {
String ns = getNS();
if (ns != null && !ns.isEmpty()) {
return ns + ":" + getName();
}
return getName();
}
@Nullable
private String getNS(){
return XmlUtil.findNamespacePrefixByURI((XmlFile) myDescriptorTag.getContainingFile(), getNamespace());
}
@Override
public String getDefaultName() {
final PsiFile psiFile = myDescriptorTag.getContainingFile();
XmlTag rootTag = psiFile instanceof XmlFile ?((XmlFile)psiFile).getRootTag():null;
if (rootTag != null && QUALIFIED_ATTR_VALUE.equals(rootTag.getAttributeValue(ELEMENT_FORM_DEFAULT))) {
return getQualifiedName();
}
return getName();
}
public boolean isAbstract() {
return isAbstractDeclaration(myDescriptorTag);
}
public static Boolean isAbstractDeclaration(final XmlTag descriptorTag) {
return Boolean.valueOf(descriptorTag.getAttributeValue("abstract"));
}
@Override
public void setName(String name) throws IncorrectOperationException {
NamedObjectDescriptor.setName(myDescriptorTag, name);
}
public void setValidator(final Validator<XmlTag> validator) {
myValidator = validator;
}
@Override
public void validate(@NotNull XmlTag context, @NotNull ValidationHost host) {
Validator<XmlTag> validator = myValidator;
if (validator != null) {
validator.validate(context, host);
}
}
@Override
public PsiReference[] getValueReferences(XmlTag xmlTag, @NotNull String text) {
XmlTagValue value = xmlTag.getValue();
XmlText[] elements = value.getTextElements();
if (elements.length == 0 || xmlTag.getSubTags().length > 0) return PsiReference.EMPTY_ARRAY;
return new PsiReference[] {
new XmlEnumeratedValueReference(xmlTag, this, ElementManipulators.getValueTextRange(xmlTag))
};
}
@Override
public boolean allowElementsFromNamespace(final String namespace, final XmlTag context) {
final TypeDescriptor type = getType(context);
if (type instanceof ComplexTypeDescriptor) {
final ComplexTypeDescriptor typeDescriptor = (ComplexTypeDescriptor)type;
return typeDescriptor.canContainTag("a", namespace, context) ||
typeDescriptor.getNsDescriptor().hasSubstitutions() ||
XmlUtil.nsFromTemplateFramework(namespace)
;
}
return false;
}
@Override
public String toString() {
return getName() + " (" + getNamespace() + ")";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
XmlElementDescriptorImpl that = (XmlElementDescriptorImpl)o;
if (myDescriptorTag != null ? !myDescriptorTag.equals(that.myDescriptorTag) : that.myDescriptorTag != null) return false;
return true;
}
@Override
public int hashCode() {
return myDescriptorTag != null ? myDescriptorTag.hashCode() : 0;
}
}