| /* |
| * 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.util; |
| |
| import com.intellij.codeInsight.daemon.XmlErrorMessages; |
| import com.intellij.javaee.ExternalResourceManager; |
| import com.intellij.javaee.ExternalResourceManagerEx; |
| import com.intellij.javaee.UriUtil; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.vfs.VfsUtilCore; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.openapi.vfs.VirtualFileManager; |
| import com.intellij.openapi.vfs.ex.http.HttpFileSystem; |
| import com.intellij.psi.PsiFile; |
| import com.intellij.psi.PsiManager; |
| import com.intellij.psi.xml.XmlFile; |
| import com.intellij.psi.xml.XmlTag; |
| import com.intellij.xml.actions.validate.ErrorReporter; |
| import com.intellij.xml.actions.validate.ValidateXmlActionHandler; |
| import com.intellij.xml.index.XmlNamespaceIndex; |
| import org.apache.xerces.xni.XMLResourceIdentifier; |
| import org.apache.xerces.xni.XNIException; |
| import org.apache.xerces.xni.parser.XMLEntityResolver; |
| import org.apache.xerces.xni.parser.XMLInputSource; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.Nullable; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.SAXParseException; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.StringReader; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| /** |
| * @author Maxim.Mossienko |
| */ |
| public class XmlResourceResolver implements XMLEntityResolver { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.xml.util.XmlResourceResolver"); |
| private final XmlFile myFile; |
| private final Project myProject; |
| private final Map<String,String> myExternalResourcesMap = new HashMap<String, String>(1); |
| private boolean myStopOnUnDeclaredResource; |
| @NonNls |
| public static final String HONOUR_ALL_SCHEMA_LOCATIONS_PROPERTY_KEY = "idea.xml.honour.all.schema.locations"; |
| private final ErrorReporter myErrorReporter; |
| |
| public XmlResourceResolver(XmlFile _xmlFile, Project _project, final ErrorReporter errorReporter) { |
| myFile = _xmlFile; |
| myProject = _project; |
| myErrorReporter = errorReporter; |
| } |
| |
| public String getPathByPublicId(String baseId) { |
| return myExternalResourcesMap.get(baseId); |
| } |
| |
| public String[] getResourcePaths() { |
| return myExternalResourcesMap.values().toArray(new String[myExternalResourcesMap.size()]); |
| } |
| |
| @Nullable |
| public PsiFile resolve(@Nullable final String baseSystemId, final String _systemId) { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("enter: resolveEntity(baseSystemId='" + baseSystemId + "' systemId='" + _systemId + "," + toString() + "')"); |
| } |
| |
| if (_systemId == null) return null; |
| if (myStopOnUnDeclaredResource && |
| ExternalResourceManagerEx.getInstanceEx().isIgnoredResource(_systemId)) { |
| throw new IgnoredResourceException(); |
| } |
| |
| final int length = XmlUtil.getPrefixLength(_systemId); |
| final String systemId = _systemId.substring(length); |
| |
| final PsiFile[] result = new PsiFile[] { null }; |
| final Runnable action = new Runnable() { |
| @Override |
| public void run() { |
| PsiFile baseFile = null; |
| VirtualFile vFile = null; |
| |
| if (baseSystemId != null) { |
| baseFile = resolve(null,baseSystemId); |
| |
| if (baseFile == null) { |
| // Find relative to myFile |
| File workingFile = new File(""); |
| String workingDir = workingFile.getAbsoluteFile().getAbsolutePath().replace(File.separatorChar, '/'); |
| String id = StringUtil.replace(baseSystemId, workingDir, myFile.getVirtualFile().getParent().getPath()); |
| vFile = UriUtil.findRelative(id, myFile); |
| |
| if (vFile == null) { |
| vFile = UriUtil.findRelative(baseSystemId, myFile); |
| |
| if (vFile == null) { |
| try { |
| vFile = VirtualFileManager.getInstance().findFileByUrl(VfsUtilCore.convertFromUrl(new URL(baseSystemId))); |
| } catch(MalformedURLException ignore) {} |
| } |
| } |
| } |
| |
| if (vFile != null && !vFile.isDirectory() && !(vFile.getFileSystem() instanceof HttpFileSystem)) { |
| baseFile = PsiManager.getInstance(myProject).findFile(vFile); |
| } |
| } |
| if (baseFile == null) { |
| baseFile = myFile; |
| } |
| |
| String version = null; |
| String tagName = null; |
| if (baseFile == myFile) { |
| XmlTag rootTag = myFile.getRootTag(); |
| if (rootTag != null) { |
| tagName = rootTag.getLocalName(); |
| version = rootTag.getAttributeValue("version"); |
| } |
| } |
| |
| PsiFile psiFile = ExternalResourceManager.getInstance().getResourceLocation(systemId, baseFile, version); |
| if (psiFile == null) { |
| psiFile = XmlUtil.findXmlFile(baseFile, systemId); |
| } |
| // autodetection |
| if (psiFile == null) { |
| psiFile = XmlNamespaceIndex.guessSchema(systemId, tagName, version, myFile); |
| if (psiFile == null) { |
| psiFile = XmlNamespaceIndex.guessDtd(systemId, myFile); |
| } |
| } |
| |
| if (psiFile == null && baseSystemId != null) { |
| String fullUrl = baseSystemId.substring( 0, baseSystemId.lastIndexOf('/') + 1 ) + systemId; |
| psiFile = XmlUtil.findXmlFile(baseFile,fullUrl); |
| } |
| |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("before relative file checking:"+psiFile+","+systemId+","+ baseSystemId+")"); |
| } |
| if (psiFile == null && baseSystemId == null) { // entity file |
| File workingFile = new File(""); |
| String workingDir = workingFile.getAbsoluteFile().getAbsolutePath().replace(File.separatorChar, '/') + "/"; |
| |
| String relativePath = StringUtil.replace( |
| systemId, |
| workingDir, |
| "" |
| ); |
| |
| if (relativePath.equals(systemId)) { |
| // on Windows systemId consisting of idea install path could become encoded DOS short name (e.g. idea%7f1.504) |
| // I am not aware how to get such name from 'workingDir' so let just pickup filename from there |
| relativePath = systemId.substring(systemId.lastIndexOf('/') + 1); |
| } |
| |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("next to relative file checking:"+relativePath+","+myExternalResourcesMap.size()+")"); |
| } |
| |
| for(String path:getResourcePaths()) { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Finding file by url:" + path); |
| } |
| VirtualFile file = VirtualFileManager.getInstance().findFileByUrl(path); |
| if (file == null) continue; |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Finding "+relativePath+" relative to:"+file.getPath()); |
| } |
| final VirtualFile relativeFile = UriUtil.findRelativeFile(relativePath, file); |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Found "+(relativeFile != null ? relativeFile.getPath():"null")); |
| } |
| |
| if (relativeFile != null) { |
| psiFile = PsiManager.getInstance(myProject).findFile(relativeFile); |
| if (psiFile != null) break; |
| } |
| } |
| } |
| |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("resolveEntity: psiFile='" + (psiFile != null ? psiFile.getVirtualFile() : null) + "'"); |
| } |
| result[0] = psiFile; |
| } |
| }; |
| ApplicationManager.getApplication().runReadAction(action); |
| |
| final PsiFile psiFile = result[0]; |
| if (psiFile != null) { |
| final VirtualFile file = psiFile.getVirtualFile(); |
| if (file != null) { |
| final String url = file.getUrl(); |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Adding external resource ref:"+systemId+","+url+","+ toString()); |
| } |
| myExternalResourcesMap.put(systemId,url); |
| } |
| } |
| return psiFile; |
| } |
| |
| @Override |
| @Nullable |
| public XMLInputSource resolveEntity(XMLResourceIdentifier xmlResourceIdentifier) throws XNIException, IOException { |
| String publicId = xmlResourceIdentifier.getLiteralSystemId() != null ? |
| xmlResourceIdentifier.getLiteralSystemId(): |
| xmlResourceIdentifier.getNamespace(); |
| |
| PsiFile psiFile = resolve(xmlResourceIdentifier.getBaseSystemId(), publicId); |
| if (psiFile == null && xmlResourceIdentifier.getBaseSystemId() != null) { |
| psiFile = ExternalResourceManager.getInstance().getResourceLocation(xmlResourceIdentifier.getBaseSystemId(), myFile, null); |
| } |
| if (psiFile==null && xmlResourceIdentifier.getLiteralSystemId()!=null && xmlResourceIdentifier.getNamespace()!=null) { |
| psiFile = resolve( |
| xmlResourceIdentifier.getBaseSystemId(), |
| publicId = xmlResourceIdentifier.getNamespace() |
| ); |
| } |
| |
| if (psiFile == null) { |
| if (publicId != null && publicId.contains(":/")) { |
| try { |
| myErrorReporter.processError( |
| new SAXParseException(XmlErrorMessages.message("xml.validate.external.resource.is.not.registered", publicId), publicId, null, 0,0), ValidateXmlActionHandler.ProblemType.ERROR); |
| } |
| catch (SAXException ignore) { |
| |
| } |
| final XMLInputSource source = new XMLInputSource(xmlResourceIdentifier); |
| source.setPublicId(publicId); |
| source.setCharacterStream(new StringReader("")); |
| return source; |
| } |
| return null; |
| } |
| |
| XMLInputSource source = new XMLInputSource(xmlResourceIdentifier); |
| if (xmlResourceIdentifier.getLiteralSystemId() == null) { |
| VirtualFile virtualFile = psiFile.getVirtualFile(); |
| if (virtualFile != null) { |
| final String url = VfsUtilCore.fixIDEAUrl(virtualFile.getUrl()); |
| source.setBaseSystemId(url); |
| source.setSystemId(url); |
| } |
| } |
| source.setPublicId(publicId); |
| source.setCharacterStream(new StringReader(psiFile.getText())); |
| |
| return source; |
| } |
| |
| public void setStopOnUnDeclaredResource(final boolean stopOnUnDeclaredResource) { |
| myStopOnUnDeclaredResource = stopOnUnDeclaredResource; |
| } |
| |
| public static class IgnoredResourceException extends RuntimeException { |
| } |
| } |