private void fetchDtd(final Project project, final String dtdUrl, final String url, final ProgressIndicator indicator) throws IOException {
    final String extResourcesPath = getExternalResourcesPath();
    final File extResources = new File(extResourcesPath);
    LOG.assertTrue(extResources.mkdirs() || extResources.exists(), extResources);

    final PsiManager psiManager = PsiManager.getInstance(project);
    ApplicationManager.getApplication().invokeAndWait(() -> WriteAction.run(() -> {
      final String path = FileUtil.toSystemIndependentName(extResources.getAbsolutePath());
      final VirtualFile vFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(path);
      LOG.assertTrue(vFile != null, path);
    }));

    final List<String> downloadedResources = new LinkedList<>();
    final List<String> resourceUrls = new LinkedList<>();
    final IOException[] nestedException = new IOException[1];

    try {
      final String resPath = fetchOneFile(indicator, url, project, extResourcesPath, null);
      if (resPath == null) return;
      resourceUrls.add(dtdUrl);
      downloadedResources.add(resPath);

      VirtualFile virtualFile = findFileByPath(resPath, dtdUrl, project);

      Set<String> processedLinks = new HashSet<>();
      Map<String, String> baseUrls = new HashMap<>();
      Map<String, String> parentRefs = new HashMap<>();
      VirtualFile contextFile = virtualFile;
      Set<String> linksToProcess = new HashSet<>(extractEmbeddedFileReferences(virtualFile, null, psiManager, url));

      while (!linksToProcess.isEmpty()) {
        String s = linksToProcess.iterator().next();
        linksToProcess.remove(s);
        processedLinks.add(s);

        final boolean absoluteUrl = s.startsWith(HTTP_PROTOCOL) || s.startsWith(HTTPS_PROTOCOL);
        String resourceUrl;
        if (absoluteUrl) {
          resourceUrl = s;
        }
        else {
          String baseUrl = baseUrls.get(s);
          if (baseUrl == null) baseUrl = url;

          resourceUrl = baseUrl.substring(0, baseUrl.lastIndexOf('/') + 1) + s;
          try {
            URL base = new URL(baseUrl);
            resourceUrl = new URL(base, s).toString();
          }
          catch (MalformedURLException e) {
            LOG.warn(e);
          }
        }

        String refName = s;
        if (absoluteUrl) {
          refName = Integer.toHexString(s.hashCode()) + "_" + refName.substring(refName.lastIndexOf('/') + 1);
        }
        else if (!refName.startsWith("/")) {
          String parentRef = parentRefs.get(refName);
          if (parentRef != null && !parentRef.startsWith("/") && parentRef.contains("/")) {
            refName = new File(new File(parentRef).getParent(), refName).getPath();
          }
        }
        String resourcePath;
        try {
          resourcePath = fetchOneFile(indicator, resourceUrl, project, extResourcesPath, refName);
        }
        catch (IOException e) {
          nestedException[0] = new FetchingResourceIOException(e, resourceUrl);
          break;
        }

        if (resourcePath == null) break;

        virtualFile = findFileByPath(resourcePath, absoluteUrl ? s : null, project);
        downloadedResources.add(resourcePath);

        if (absoluteUrl) {
          resourceUrls.add(s);
        }

        final Set<String> newLinks = extractEmbeddedFileReferences(virtualFile, contextFile, psiManager, resourceUrl);
        for (String u : newLinks) {
          baseUrls.put(u, resourceUrl);
          parentRefs.put(u, refName);
          if (!processedLinks.contains(u)) linksToProcess.add(u);
        }
      }
    }
    catch (IOException ex) {
      nestedException[0] = ex;
    }
    if (nestedException[0] != null) {
      cleanup(resourceUrls, downloadedResources);
      throw nestedException[0];
    }
  }
private void fetchDtd(final Project project, final String dtdUrl, final String url, final ProgressIndicator indicator)