| /* |
| * 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; |
| |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.editor.ScrollType; |
| import com.intellij.openapi.module.ModuleUtilCore; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.PsiFile; |
| import com.intellij.psi.XmlElementFactory; |
| import com.intellij.psi.xml.*; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.xml.index.XmlNamespaceIndex; |
| import com.intellij.xml.index.XmlTagNamesIndex; |
| import com.intellij.xml.util.XmlUtil; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.*; |
| |
| /** |
| * @author yole |
| */ |
| public class DefaultXmlNamespaceHelper extends XmlNamespaceHelper { |
| private static final Logger LOG = Logger.getInstance(DefaultXmlNamespaceHelper.class); |
| |
| @Override |
| protected boolean isAvailable(PsiFile file) { |
| return true; |
| } |
| |
| @Override |
| public void insertNamespaceDeclaration(@NotNull final XmlFile file, |
| @Nullable final Editor editor, |
| @NotNull final Set<String> possibleNamespaces, |
| @Nullable String nsPrefix, |
| @Nullable final Runner<String, IncorrectOperationException> runAfter) throws IncorrectOperationException { |
| |
| final String namespace = possibleNamespaces.iterator().next(); |
| |
| final Project project = file.getProject(); |
| final XmlTag rootTag = file.getRootTag(); |
| assert rootTag != null; |
| XmlAttribute anchor = getAnchor(rootTag); |
| |
| final List<XmlSchemaProvider> providers = XmlSchemaProvider.getAvailableProviders(file); |
| String prefix = getPrefix(file, nsPrefix, namespace, providers); |
| |
| final XmlElementFactory elementFactory = XmlElementFactory.getInstance(project); |
| String location = getLocation(file, namespace, providers); |
| String xsiPrefix = null; |
| if (location != null) { |
| xsiPrefix = rootTag.getPrefixByNamespace(XmlUtil.XML_SCHEMA_INSTANCE_URI); |
| if (xsiPrefix == null) { |
| xsiPrefix = "xsi"; |
| rootTag.add(elementFactory.createXmlAttribute("xmlns:xsi", XmlUtil.XML_SCHEMA_INSTANCE_URI)); |
| } |
| } |
| |
| @NonNls final String qname = "xmlns" + (prefix.length() > 0 ? ":"+ prefix :""); |
| final XmlAttribute attribute = elementFactory.createXmlAttribute(qname, namespace); |
| if (anchor == null) { |
| rootTag.add(attribute); |
| } else { |
| rootTag.addAfter(attribute, anchor); |
| } |
| |
| if (location != null) { |
| XmlAttribute locationAttribute = rootTag.getAttribute(XmlUtil.SCHEMA_LOCATION_ATT, XmlUtil.XML_SCHEMA_INSTANCE_URI); |
| final String pair = namespace + " " + location; |
| if (locationAttribute == null) { |
| locationAttribute = elementFactory.createXmlAttribute(xsiPrefix + ":" + XmlUtil.SCHEMA_LOCATION_ATT, pair); |
| rootTag.add(locationAttribute); |
| } |
| else { |
| final String value = locationAttribute.getValue(); |
| if (!StringUtil.notNullize(value).contains(namespace)) { |
| if (value == null || StringUtil.isEmptyOrSpaces(value)) { |
| locationAttribute.setValue(pair); |
| } |
| else { |
| locationAttribute.setValue(value.trim() + " " + pair); |
| } |
| } |
| } |
| } |
| XmlUtil.reformatTagStart(rootTag); |
| |
| if (editor != null && namespace.length() == 0) { |
| final XmlAttribute xmlAttribute = rootTag.getAttribute(qname); |
| if (xmlAttribute != null) { |
| final XmlAttributeValue value = xmlAttribute.getValueElement(); |
| assert value != null; |
| final int startOffset = value.getTextOffset(); |
| editor.getCaretModel().moveToOffset(startOffset); |
| editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); |
| } |
| } |
| if (runAfter != null) { |
| runAfter.run(prefix); |
| } |
| } |
| |
| private static String getPrefix(XmlFile file, String nsPrefix, String namespace, List<XmlSchemaProvider> providers) { |
| String prefix = nsPrefix; |
| if (prefix == null) { |
| for (XmlSchemaProvider provider : providers) { |
| prefix = provider.getDefaultPrefix(namespace, file); |
| if (prefix != null) { |
| break; |
| } |
| } |
| } |
| if (prefix == null) { |
| prefix = ""; |
| } |
| return prefix; |
| } |
| |
| private static XmlAttribute getAnchor(XmlTag rootTag) { |
| final XmlAttribute[] attributes = rootTag.getAttributes(); |
| XmlAttribute anchor = null; |
| for (XmlAttribute attribute : attributes) { |
| final XmlAttributeDescriptor descriptor = attribute.getDescriptor(); |
| if (attribute.isNamespaceDeclaration() || (descriptor != null && descriptor.isRequired())) { |
| anchor = attribute; |
| } else { |
| break; |
| } |
| } |
| return anchor; |
| } |
| |
| private static String getLocation(XmlFile file, String namespace, List<XmlSchemaProvider> providers) { |
| String location = null; |
| if (namespace.length() > 0) { |
| for (XmlSchemaProvider provider : providers) { |
| Set<String> locations = provider.getLocations(namespace, file); |
| if (locations != null && !locations.isEmpty()) { |
| location = locations.iterator().next(); |
| } |
| } |
| } |
| return location; |
| } |
| |
| @Override |
| @NotNull |
| public Set<String> guessUnboundNamespaces(@NotNull final PsiElement element, @NotNull XmlFile file) { |
| if (!(element instanceof XmlTag)) { |
| return Collections.emptySet(); |
| } |
| final XmlTag tag = (XmlTag)element; |
| final String name = tag.getLocalName(); |
| final Set<String> byTagName = getNamespacesByTagName(name, file); |
| if (!byTagName.isEmpty()) { |
| Set<String> filtered = new HashSet<String>(byTagName); |
| filtered.removeAll(Arrays.asList(tag.knownNamespaces())); |
| return filtered; |
| } |
| final Set<String> set = guessNamespace(file, name); |
| set.removeAll(Arrays.asList(tag.knownNamespaces())); |
| |
| final XmlTag parentTag = tag.getParentTag(); |
| ns: for (Iterator<String> i = set.iterator(); i.hasNext();) { |
| final String s = i.next(); |
| final Collection<XmlFile> namespaces = XmlUtil.findNSFilesByURI(s, element.getProject(), ModuleUtilCore.findModuleForPsiElement(file)); |
| for (XmlFile namespace : namespaces) { |
| final XmlDocument document = namespace.getDocument(); |
| assert document != null; |
| final XmlNSDescriptor nsDescriptor = (XmlNSDescriptor)document.getMetaData(); |
| assert nsDescriptor != null; |
| if (parentTag != null) { |
| continue ns; |
| } |
| final XmlElementDescriptor[] descriptors = nsDescriptor.getRootElementsDescriptors(document); |
| for (XmlElementDescriptor descriptor : descriptors) { |
| if (descriptor == null) { |
| LOG.error(nsDescriptor + " returned null element for getRootElementsDescriptors() array"); |
| } |
| if (descriptor.getName().equals(name)) { |
| continue ns; |
| } |
| } |
| } |
| i.remove(); |
| } |
| return set; |
| } |
| |
| private static Set<String> guessNamespace(final PsiFile file, String tagName) { |
| final Project project = file.getProject(); |
| final Collection<VirtualFile> files = XmlTagNamesIndex.getFilesByTagName(tagName, project); |
| final Set<String> possibleUris = new LinkedHashSet<String>(files.size()); |
| for (VirtualFile virtualFile : files) { |
| final String namespace = XmlNamespaceIndex.getNamespace(virtualFile, project, file); |
| if (namespace != null) { |
| possibleUris.add(namespace); |
| } |
| } |
| return possibleUris; |
| } |
| |
| @Override |
| @NotNull |
| public Set<String> getNamespacesByTagName(@NotNull final String tagName, @NotNull final XmlFile context) { |
| final List<XmlSchemaProvider> providers = XmlSchemaProvider.getAvailableProviders(context); |
| |
| HashSet<String> set = new HashSet<String>(); |
| for (XmlSchemaProvider provider : providers) { |
| set.addAll(provider.getAvailableNamespaces(context, tagName)); |
| } |
| return set; |
| } |
| |
| |
| } |