// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.daemon;

import com.intellij.application.options.XmlSettings;
import com.intellij.codeInsight.completion.CodeCompletionHandlerBase;
import com.intellij.codeInsight.completion.CompletionType;
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
import com.intellij.codeInsight.daemon.impl.analysis.*;
import com.intellij.codeInsight.lookup.LookupManager;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.htmlInspections.*;
import com.intellij.ide.DataManager;
import com.intellij.ide.highlighter.*;
import com.intellij.javaee.ExternalResourceManagerEx;
import com.intellij.javaee.ExternalResourceManagerExImpl;
import com.intellij.javaee.UriUtil;
import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.lang.ant.dom.AntResolveInspection;
import com.intellij.model.psi.PsiSymbolReference;
import com.intellij.model.psi.PsiSymbolService;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.IdeActions;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.EditorModificationUtil;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.highlighter.EditorHighlighter;
import com.intellij.openapi.editor.highlighter.HighlighterIterator;
import com.intellij.openapi.util.RecursionManager;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.Navigatable;
import com.intellij.psi.*;
import com.intellij.psi.impl.include.FileIncludeManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.*;
import com.intellij.testFramework.DumbModeTestUtils;
import com.intellij.testFramework.InspectionsKt;
import com.intellij.testFramework.PlatformTestUtil;
import com.intellij.tools.ide.metrics.benchmark.Benchmark;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.xml.XmlAttributeDescriptor;
import com.intellij.xml.XmlBundle;
import com.intellij.xml.XmlElementDescriptor;
import com.intellij.xml.actions.validate.ValidateXmlActionHandler;
import com.intellij.xml.impl.schema.XmlElementDescriptorImpl;
import com.intellij.xml.util.*;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static com.intellij.model.psi.PsiSymbolReference.getReferenceText;

@SuppressWarnings({"HardCodedStringLiteral", "ConstantConditions"})
public class XmlHighlightingTest extends DaemonAnalyzerTestCase {
  private static final String BASE_PATH = "/xml/";

  private boolean myTestJustJaxpValidation;
  @NonNls private static final String CREATE_NAMESPACE_DECLARATION_INTENTION_NAME = "Create namespace declaration";
  private boolean old;
  private String myOldDoctype;

  private void doTest() throws Exception {
    doTest(false);
  }
  private void doTest(boolean checkWarnings) throws Exception {
    XmlHighlightVisitor.setDoJaxpTesting(myTestJustJaxpValidation);
    try {
      doTest(getFullRelativeTestName(), checkWarnings, false);
    }
    finally {
      XmlHighlightVisitor.setDoJaxpTesting(false);
    }
  }

  private String getFullRelativeTestName() {
    return getFullRelativeTestName(".xml");
  }

  private String getFullRelativeTestName(String ext) {
    return BASE_PATH + getTestName(false) + ext;
  }

  @Override
  protected boolean forceExternalValidation() {
    return myTestJustJaxpValidation;
  }

  public void testclosedTag1() throws Exception { doTest(); }
  public void testClosedTag2() throws Exception { doTest(); }
  public void testwrongRootTag1() throws Exception { doTest(); }
  public void testrootTag1() throws Exception { doTest(); }
  public void testManyRootTags() throws Exception { doTest(); }
  public void testCommentBeforeProlog() throws Exception { doTest(); }
  public void testXmlStylesheet() throws Exception { doTest(); }
  public void testCommentBeforeProlog_2() throws Exception { doTest(); }
  //public void testNoRootTag() throws Exception { doTest(); }

  public void testduplicateAttribute() throws Exception { doTest(); }

  public void testduplicateAttribute2() {
    configureByFiles(null, BASE_PATH + getTestName(false) + ".xml", BASE_PATH + getTestName(false) + ".xsd");

    final String url = "http://www.foo.org/schema";
    ExternalResourceManagerExImpl.registerResourceTemporarily(url, getTestName(false) + ".xsd", getTestRootDisposable());
    final String url2 = "http://www.bar.org/foo";
    ExternalResourceManagerExImpl.registerResourceTemporarily(url2, getTestName(false) + ".xsd", getTestRootDisposable());
    final Collection<HighlightInfo> infoCollection = doDoTest(true, false, true);
    final TextRange startTagNameRange = XmlTagUtil.getStartTagNameElement(((XmlFile)myFile).getDocument().getRootTag()).getTextRange();

    HighlightInfo infoAtTagName = null;

    for(HighlightInfo info:infoCollection) {
      if (info.startOffset == startTagNameRange.getStartOffset() && info.endOffset == startTagNameRange.getEndOffset()) {
        if (info.getDescription()
          .equals("Attribute \"foo\" bound to namespace \"http://www.w3.org/2000/xmlns/\" was already specified for element \"root\".")) {
          infoAtTagName = info;
          break;
        }
      }
    }

    assertNotNull( infoAtTagName );
  }

  // TODO: external validator should not be launched due to error detected after general highlighting pass!
  @HighlightingFlags(HighlightingFlag.SkipExternalValidation)
  public void testDuplicateIdAttribute() throws Exception { doTest(); }
  public void testDuplicateIdAttribute2() throws Exception {
    doTest();
  }
  public void testDuplicateNameAttribute() throws Exception {
    doSchemaTestWithManyFilesFromSeparateDir(
      new String[][] {
        {"http://www.springframework.org/schema/beans/spring-beans-2.5.xsd", "spring-beans-2.5.xsd"},
      },
      null
    );
  }

  @HighlightingFlags(HighlightingFlag.SkipExternalValidation)
  public void testInvalidIdRefAttribute() throws Exception { doTest(); }
  public void testEntityRefWithNoDtd() throws Exception { doTest(); }
  public void testNoSpaceBeforeAttrAndNoCdataEnd() throws Exception { doTest(); }

  // TODO: external validator should not be launched due to error detected after general highlighting pass!
  @HighlightingFlags(HighlightingFlag.SkipExternalValidation)
  public void testEntityRefWithEmptyDtd() throws Exception { doTest(); }
  @HighlightingFlags(HighlightingFlag.SkipExternalValidation)
  public void testEmptyNSRef() throws Exception { doTest(); }

  @HighlightingFlags(HighlightingFlag.SkipExternalValidation)
  public void testDoctypeWithoutSchema() throws Exception {
    final String baseName = BASE_PATH + getTestName(false);

    configureByFiles(null, findVirtualFile(baseName + ".xml"), findVirtualFile(baseName + ".ent"));
    doDoTest(true,false);
    myFile.accept(new XmlRecursiveElementVisitor() {
      @Override
      public void visitXmlAttributeValue(@NotNull XmlAttributeValue value) {
        final PsiElement[] children = value.getChildren();
        for (PsiElement child : children) {
          if (child instanceof XmlEntityRef) {
            PsiElement psiElement = child.getReferences()[0].resolve();
            assertNotNull(psiElement);
            assertEquals(getTestName(false) + ".ent", psiElement.getContainingFile().getVirtualFile().getName());
            assertTrue(((Navigatable)psiElement).canNavigate());
          }
        }
      }
    });
  }

  public void testSvg() throws Exception {
    doTest(getFullRelativeTestName(".svg"), true, false);
  }

  public void testSvg20() throws Exception {
    InspectionsKt.enableInspectionTools(getProject(), getTestRootDisposable(), new XmlDefaultAttributeValueInspection());
    doTest(getFullRelativeTestName(".svg"), true, false);
  }

  public void testNavigateToDeclDefinedWithEntity() throws Exception {
    final String baseName = BASE_PATH + getTestName(false);

    configureByFiles(null, findVirtualFile(baseName + ".xml"), findVirtualFile(baseName + ".dtd"), findVirtualFile(baseName + ".ent"));
    doDoTest(true,false);
    final List<PsiReference> refs = new ArrayList<>();
    myFile.accept(new XmlRecursiveElementVisitor() {
      @Override
      public void visitXmlAttribute(final @NotNull XmlAttribute attribute) {
        refs.add(attribute.getReference());
      }

      @Override
      public void visitXmlTag(final @NotNull XmlTag tag) {
        refs.add(tag.getReference());
        super.visitXmlTag(tag);
      }
    });

    assertEquals(2, refs.size());

    for(PsiReference ref:refs) {
      final PsiElement element = ref.resolve();
      // In order to navigate to entity defined things we need correctly setup navigation element among other things
      assertTrue(element instanceof PsiNamedElement);
      assertSame(element.getNavigationElement(), element);
      assertTrue(!element.isPhysical());

      final PsiElement original = element.getOriginalElement();
      // TODO: fix XmlAttlistDecl to return proper original element
      if (original != element && !(element instanceof XmlAttlistDecl)) {
        assertNotNull(original);
        assertTrue(original.isPhysical());
      }
    }
  }

  public void testDoctypeWithoutSchema2() throws Exception {
    final String baseName = BASE_PATH + "DoctypeWithoutSchema";

    configureByFiles(null, findVirtualFile(baseName + ".xml"), findVirtualFile(baseName + ".ent"));
    doDoTest(true, false);
  }

  public void testComplexSchemaValidation8() throws IOException {
    final String testName = getTestName(false);
    String location = testName +".xsd";
    String location2 = testName +"_2.xsd";
    String url = "http://drools.org/rules";
    String url2 = "http://drools.org/semantics/groovy";

    ExternalResourceManagerExImpl.registerResourceTemporarily(url, location, getTestRootDisposable());
    ExternalResourceManagerExImpl.registerResourceTemporarily(url2, location2, getTestRootDisposable());
    final String basePath = BASE_PATH + testName;
    configureByFiles(null, findVirtualFile(basePath + ".xml"), findVirtualFile(basePath + ".xsd"), findVirtualFile(basePath + "_2.xsd"));
    doDoTest(true,false);

  }

  public void testComplexSchemaValidation9() throws IOException {
    final String basePath = BASE_PATH + getTestName(false);

    configureByFiles(null, findVirtualFile(basePath + ".xml"), findVirtualFile(basePath + ".xsd"));
    doDoTest(true,false);
  }

  public void testComplexSchemaValidation10() throws IOException {
    final String testName = getTestName(false);

    final String basePath = BASE_PATH + testName;
    configureByFiles(null, findVirtualFile(basePath + ".xsd"), findVirtualFile(basePath + "_2.xsd"));
    doDoTest(true,false);

    final List<PsiReference> refs = new ArrayList<>(2);

    myFile.acceptChildren(new XmlRecursiveElementVisitor() {

      @Override public void visitXmlTag(@NotNull XmlTag tag) {
        super.visitXmlTag(tag);

        addRefsInPresent(tag, "base", refs);
        if ("group".equals(tag.getLocalName())) {
          addRefsInPresent(tag, "ref", refs);
        }
      }
    });

    assertEquals(2, refs.size());
    PsiElement psiElement = refs.get(0).resolve();
    assertNotNull(psiElement);
    assertEquals(getTestName(false) + "_2.xsd",psiElement.getContainingFile().getName());

    psiElement = refs.get(1).resolve();
    assertNotNull(psiElement);
    assertEquals(getTestName(false) + "_2.xsd",psiElement.getContainingFile().getName());
  }

  private static void addRefsInPresent(final XmlTag tag, final String name, final List<? super PsiReference> refs) {
    if (tag.getAttributeValue(name) != null) {
      ContainerUtil.addAll(refs, tag.getAttribute(name, null).getValueElement().getReferences());
    }
  }

  public void testComplexSchemaValidation11() {
    final String testName = getTestName(false);
    doTestWithLocations(
      new String[][] {
        {"http://www.springframework.org/schema/beans",testName + ".xsd"},
        {"http://www.springframework.org/schema/util",testName + "_2.xsd"}
      },
      "xml"
    );
  }

  private void doTestWithLocations(String[] @Nullable [] resources, String ext) {
    doConfigureWithLocations(resources, ext);
    doDoTest(true,false);
  }

  private void doConfigureWithLocations(final String[][] resources, final String ext) {

    String[] testNames = new String[(resources != null? resources.length:0) + 1];
    testNames[0] = BASE_PATH + getTestName(false) + "." + ext;

    if (resources != null) {
      int curResource = 0;
      for(String[] resource:resources) {
        ExternalResourceManagerExImpl
          .registerResourceTemporarily(resource[0], getTestDataPath() + BASE_PATH + resource[1], getTestRootDisposable());
        testNames[++curResource] = BASE_PATH + resource[1];
      }
    }

    configureByFiles(
      null,
      testNames
    );
  }

  public void testComplexSchemaValidation12() throws Exception {
    final String basePath = BASE_PATH + getTestName(false);
    configureByFile(
      basePath + ".xsd",
      null
    );
    doDoTest(true,false);
  }

  public void testInvalidIdRefInXsl() throws Exception {
    doTest(getFullRelativeTestName(), false, false);
  }

  public void testXercesMessageOnPI() throws Exception {
    try {
      myTestJustJaxpValidation = true;
      doTest();
    } finally {
      myTestJustJaxpValidation = false;
    }
  }
  public void testXercesMessageOnDtd() throws Exception { doTest(); }
  public void testXercesMessagesBinding5() throws Exception {
    try {
      myTestJustJaxpValidation = true;
      doTest();
    } finally {
      myTestJustJaxpValidation = false;
    }
  }

  public void testXercesCachingProblem() {
    final String[][] resources = {{"http://www.test.com/test", getTestName(false) + ".dtd"}};
    doConfigureWithLocations(resources, "xml");

    doDoTest(true,true);
    WriteCommandAction.runWriteCommandAction(null, () -> myEditor.getDocument().insertString(myEditor.getDocument().getCharsSequence().toString().indexOf("?>") + 2, "\n"));

    doDoTest(true,true);
  }

  public void testComplexSchemaValidation13() {
    doTestWithLocations(new String[][] { {"urn:test", getTestName(false) + "_2.xsd"} }, "xsd");
  }

  public void testComplexSchemaValidation14() {
    doTestWithLocations(new String[][]{{"parent", getTestName(false) + ".xsd"}}, "xml");
  }

  public void testComplexSchemaValidation14_2() throws Exception {
    doTest(getFullRelativeTestName(".xsd"), true, false);
  }

  public void testComplexSchemaValidation15() {
    doTestWithLocations(
      new String[][] {
        {"http://www.linkedin.com/lispring", getTestName(false) + ".xsd"},
        {"http://www.springframework.org/schema/beans", getTestName(false) + "_2.xsd"}
      },
      "xml"
    );
  }

  @HighlightingFlags(HighlightingFlag.SkipExternalValidation)
  public void testComplexSchemaValidation16() {
    doTestWithLocations(
      new String[][] {
        {"http://www.inversoft.com/schemas/savant-2.0/project", getTestName(false) + ".xsd"},
        {"http://www.inversoft.com/schemas/savant-2.0/base", getTestName(false) + "_2.xsd"}
      },
      "xml"
    );
  }

  public void testComplexSchemaValidation17() {
    doTestWithLocations(
      new String[][]{
        {"urn:test", getTestName(false) + ".xsd"}
      },
      "xml"
    );
  }

  @HighlightingFlags(HighlightingFlag.SkipExternalValidation)
  public void testSchemaReferencesValidation() throws Exception {
    doTest(getFullRelativeTestName(".xsd"), false, false);
  }

  public void testXhtmlSchemaHighlighting() throws Exception {
    disableHtmlSupport();
    try {
      configureByFiles(null, findVirtualFile(BASE_PATH + "xhtml1-transitional.xsd"), findVirtualFile(BASE_PATH + "xhtml-special.ent"),
                       findVirtualFile(BASE_PATH + "xhtml-symbol.ent"), findVirtualFile(BASE_PATH + "xhtml-lat1.ent"));
      doDoTest(true,false);
    }
    finally {
      enableHtmlSupport();
    }
  }

  public void testSchemaValidation() throws Exception {
    doTest();
  }

  public void testSchemaValidation2() throws Exception {
    configureByFiles(null, findVirtualFile(BASE_PATH + getTestName(false) + ".xml"),
                     findVirtualFile(BASE_PATH + getTestName(false) + ".xsd"));
    doDoTest(true,false);

    final XmlTag rootTag = ((XmlFile)myFile).getDocument().getRootTag();
    final XmlAttributeValue valueElement = rootTag.getAttribute("noNamespaceSchemaLocation", XmlUtil.XML_SCHEMA_INSTANCE_URI).getValueElement();
    final PsiReference[] references = valueElement.getReferences();

    final String expectedFileName = getTestName(false) + ".xsd";

    assertEquals(1, references.length);
    final PsiElement schema = references[0].resolve();
    assertNotNull(schema);

    assertEquals(expectedFileName,schema.getContainingFile().getName());

    final PsiReference referenceAt = myFile.findReferenceAt(myEditor.getCaretModel().getOffset());
    assertNotNull(referenceAt);
    final PsiElement psiElement = referenceAt.resolve();
    assertTrue(psiElement instanceof XmlTag &&
               "complexType".equals(((XmlTag)psiElement).getLocalName()) &&
               "Y".equals(((XmlTag)psiElement).getAttributeValue("name"))
               );

    assertEquals(expectedFileName,psiElement.getContainingFile().getName());
  }

  public void testSchemaValidation3() throws Exception {
    String location = getTestName(false)+".xsd";
    String location2 = getTestName(false)+"_2.xsd";
    String url = "parent";
    String url2 = "child";

    ExternalResourceManagerExImpl.registerResourceTemporarily(url, location, getTestRootDisposable());
    ExternalResourceManagerExImpl.registerResourceTemporarily(url2, location2, getTestRootDisposable());

    configureByFiles(null, findVirtualFile(BASE_PATH + getTestName(false) + ".xml"), findVirtualFile(BASE_PATH + location),
                     findVirtualFile(BASE_PATH + location2));
    doDoTest(true,false);

  }

  public void testSchemaValidation4() throws Exception {
    String schemaLocation = getTestName(false)+".xsd";

    configureByFiles(null, findVirtualFile(BASE_PATH + getTestName(false) + ".xml"), findVirtualFile(BASE_PATH + schemaLocation));

    ExternalResourceManagerExImpl.registerResourceTemporarily(schemaLocation, schemaLocation, getTestRootDisposable());
    doDoTest(true, false);
  }

  public void testSchemaValidation5() throws Exception {
    String schemaLocation = getTestName(false)+".xsd";

    configureByFile(BASE_PATH + schemaLocation);
    doDoTest(true, false);
    final List<PsiReference> myTypeOrElementRefs = new ArrayList<>(1);
    final List<XmlTag> myTypesAndElementDecls = new ArrayList<>(1);

    myFile.accept(new XmlRecursiveElementVisitor() {
      @Override public void visitXmlAttributeValue(@NotNull XmlAttributeValue value) {
        final PsiElement parent = value.getParent();
        if (!(parent instanceof XmlAttribute)) return;
        final String name = ((XmlAttribute)parent).getName();
        if ("type".equals(name) || "base".equals(name) || "ref".equals(name)) {
          myTypeOrElementRefs.add(value.getReferences()[0]);
        }
      }

      @Override public void visitXmlTag(@NotNull XmlTag tag) {
        super.visitXmlTag(tag);
        final String localName = tag.getLocalName();
        if ("complexType".equals(localName) || "simpleType".equals(localName) || "element".equals(localName)) {
          if (tag.getAttributeValue("name") != null) myTypesAndElementDecls.add(tag);
        }
      }
    });

    assertEquals(9,myTypesAndElementDecls.size());
    assertEquals(5,myTypeOrElementRefs.size());
    for(XmlTag t:myTypesAndElementDecls) {
      final XmlAttribute attribute = t.getAttribute("name", null);
      final XmlAttributeValue valueElement = attribute.getValueElement();
      final PsiReference nameReference = valueElement.getReferences()[0];
      WriteCommandAction.runWriteCommandAction(null, () -> {
        nameReference.handleElementRename("zzz");
      });
    }

    for(PsiReference ref:myTypeOrElementRefs)  {
      assertNull(ref.resolve());
    }
  }

  public void testExternalValidatorOnValidXmlWithNamespacesNotSetup() throws Exception {
    List<String> list = new ArrayList<>();
    list.add("http://xml.apache.org/axis/wsdd2/");
    list.add("http://xml.apache.org/axis/wsdd2/providers/java");
    list.add("http://soapinterop.org/xsd2");
    ExternalResourceManagerEx.getInstanceEx().addIgnoredResources(list, getTestRootDisposable());

    doTest(getFullRelativeTestName(".xml"), true, false);
  }

  @HighlightingFlags(HighlightingFlag.SkipExternalValidation)
  public void testExternalValidatorOnValidXmlWithNamespacesNotSetup2() throws Exception {
    ExternalResourceManagerEx.getInstanceEx().addIgnoredResources(Collections.singletonList(""), getTestRootDisposable());
    doTest(getFullRelativeTestName(".xml"), true, false);
  }

  public void testXercesMessagesBinding() throws Exception {
    doTest(getFullRelativeTestName(".xml"), true, false);
  }

  public void testXercesMessagesBinding3() throws Exception {
    doTest(getFullRelativeTestName(".xml"), true, false);
  }

  public void testXercesMessagesBinding4() {
    String url = "antlib:org.apache.maven.artifact.ant";

    enableInspectionTool(new AntResolveInspection());
    doTestWithLocations(new String[][]{{url, getTestName(false) + ".xsd"}}, "xml");
  }

  public void testWrongRegExpInSchema() throws Exception {
    doTest(getFullRelativeTestName(".xsd"), true, false);
    doTest(getFullRelativeTestName("2.xsd"), true, false);
  }

  public void testWrongRegExpCategory() throws Exception {
    doTest(getFullRelativeTestName(".xsd"), true, false);
  }

  public void testXercesMessagesBinding2() throws Exception {
    final String url = getTestName(false) + ".xsd";
    ExternalResourceManagerExImpl.registerResourceTemporarily(url, url, getTestRootDisposable());

    doTest(
      new VirtualFile[] {
        findVirtualFile(getFullRelativeTestName(".xml")),
        findVirtualFile(getFullRelativeTestName(".xsd"))
      },
      true,
      false
    );

    final String url2 = getTestName(false) + "_2.xsd";

    ExternalResourceManagerExImpl.registerResourceTemporarily(url2, url2, getTestRootDisposable());

    doTest(
      new VirtualFile[] {
        findVirtualFile(getFullRelativeTestName("_2.xml")),
        findVirtualFile(getFullRelativeTestName("_2.xsd"))
      },
      true,
      false
    );

  }

  public void testValidationOfDtdFile() throws Exception {
    doTest(getFullRelativeTestName(".dtd"), false, false);
  }

  public void testXHtmlValidation() throws Exception {
    doTest();
  }

  public void testXHtmlValidation2() throws Exception {
    configureByFile(getFullRelativeTestName());
    doDoTest(true, true, true);
  }

  public void testXHtmlValidation3() throws Exception {
    doManyFilesFromSeparateDirTest(
      "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd",
      "xhtml-basic11.dtd",
      () -> {
        final List<XmlAttribute> attrs = new ArrayList<>();

        myFile.acceptChildren(new XmlRecursiveElementVisitor() {
          @Override
          public void visitXmlAttribute(final @NotNull XmlAttribute attribute) {
            if (attribute.getDescriptor() != null) attrs.add(attribute);
          }
        });

        assertEquals(8, attrs.size());
        for (XmlAttribute a : attrs) {
          final PsiElement element = a.getDescriptor().getDeclaration();
          assertTrue(((Navigatable)element).canNavigate());
        }
      }
    );
  }

  public void testReferencingTargetNamespace() throws Exception {
    doTest();
  }

  public void testWebApp() throws Exception {
    doTest();
  }

  public void testEnumeratedAttributesValidation() throws Exception {
    doTest(BASE_PATH + getTestName(false) + ".xsd", false, false);
  }

  public void testStackOverflow() throws Exception {
    String location = "relaxng.xsd";
    String url = "http://relaxng.org/ns/structure/fake/1.0";
    ExternalResourceManagerExImpl.registerResourceTemporarily(url, location, getTestRootDisposable());
    doTest(new VirtualFile[]{findVirtualFile(getFullRelativeTestName()), findVirtualFile(BASE_PATH + location)}, false, false);
  }

  public void testStackOverflow2() throws Exception {
    RecursionManager.disableMissedCacheAssertions(getTestRootDisposable());
    final String url = "urn:aaa";
    final String location = getTestName(false) + ".xsd";
    ExternalResourceManagerExImpl.registerResourceTemporarily(url, location, getTestRootDisposable());
    doTest(getFullRelativeTestName(".xsd"), false, false);

  }

  public void testComplexSchemaValidation() throws Exception {
    doTest(getFullRelativeTestName(), false, false);
  }

  public void testComplexDtdValidation() throws Exception {
    String location = "Tapestry_3_0.dtd";
    String url = "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd";
    ExternalResourceManagerExImpl.registerResourceTemporarily(url, location, getTestRootDisposable());

    myTestJustJaxpValidation = true;
    doTest(new VirtualFile[] {findVirtualFile(getFullRelativeTestName()), findVirtualFile(BASE_PATH + location)}, false, false);
    myTestJustJaxpValidation = false;
  }

  public void testComplexDtdValidation2() throws Exception {
    String location = getTestName(false)+".dtd";
    ExternalResourceManagerExImpl.registerResourceTemporarily(location, location, getTestRootDisposable());

    myTestJustJaxpValidation = true;
    doTest(new VirtualFile[] {findVirtualFile(getFullRelativeTestName()), findVirtualFile(BASE_PATH + location)}, false, false);
    myTestJustJaxpValidation = false;
  }

  public void testComplexSchemaValidation2() throws Exception {
    doTest(new VirtualFile[] {findVirtualFile(getFullRelativeTestName()), findVirtualFile(BASE_PATH + "jdo_2_0.xsd")}, false, false);
  }

  public void testComplexSchemaValidation3() throws Exception {
    List<VirtualFile> files = new ArrayList<>();
    files.add(findVirtualFile(getFullRelativeTestName()));

    final VirtualFile virtualFile = findVirtualFile(BASE_PATH + "ComplexSchemaValidation3Schemas");
    ContainerUtil.addAll(files, virtualFile.getChildren());

    doTest(VfsUtilCore.toVirtualFileArray(files), true, false);
  }

  public void testComplexSchemaValidation4() throws Exception {
    String[][] urls = {{"http://www.unece.org/etrades/unedocs/repository/codelists/xml/CountryCode.xsd"},
      {"http://www.swissdec.ch/schema/sd/20050902/SalaryDeclarationServiceTypes"},
      {"http://www.swissdec.ch/schema/sd/20050902/SalaryDeclarationContainer"},
      {"http://www.swissdec.ch/schema/sd/20050902/SalaryDeclaration"}};

    doSchemaTestWithManyFilesFromSeparateDir(urls, null);
  }

  public void testComplexSchemaValidation5() throws Exception {
    String location = getTestName(false)+".xsd";
    String url = "http://www.etas.com/TELEGY/Test";
    ExternalResourceManagerExImpl.registerResourceTemporarily(url, location, getTestRootDisposable());
    doTest(new VirtualFile[] {findVirtualFile(getFullRelativeTestName()), findVirtualFile(BASE_PATH + location)}, false, false);

    final XmlTag[] subTags = ((XmlFile)myFile).getDocument().getRootTag().getSubTags();

    for(XmlTag t:subTags) {
      final PsiReference reference = t.getReference();
      assertNotNull(reference);
      final PsiElement psiElement = reference.resolve();
      assertTrue(psiElement instanceof XmlTag && "xsd:element".equals(((XmlTag)psiElement).getName()));
    }

  }

  @Override
  protected boolean doExternalValidation() {
    return !methodOfTestHasAnnotation(getClass(), getTestName(false), HighlightingFlag.SkipExternalValidation) && super.doExternalValidation();
  }

  public void testComplexSchemaValidation6() throws Exception {
    String location = getTestName(false)+".xsd";
    String url = "http://abcde/pg.html";
    ExternalResourceManagerExImpl.registerResourceTemporarily(url, location, getTestRootDisposable());
    doTest(new VirtualFile[]{findVirtualFile(getFullRelativeTestName()), findVirtualFile(BASE_PATH + location)}, true, false);
  }

  public void testComplexSchemaValidation7() throws Exception {
    String[][] urlLocationPairs = {
      {"http://schemas.xmlsoap.org/wsdl/","wsdl.xsd"},
      {"http://schemas.xmlsoap.org/wsdl/soap/","soap.xsd"},
      {"http://schemas.xmlsoap.org/soap/encoding/","encoding.xsd"},
      {"http://schemas.xmlsoap.org/wsdl/mime/","mime.xsd"},
      {"http://schemas.xmlsoap.org/wsdl/http/","http.xsd"}
    };


    doSchemaTestWithManyFilesFromSeparateDir(
      urlLocationPairs,
      files -> {
        try {
          files.set(0, findVirtualFile(BASE_PATH + getTestName(false) + "_2.xml"));
          doTest(VfsUtilCore.toVirtualFileArray(files), true, false);
          return true;
        }
        catch (Exception e) {
          throw new RuntimeException(e);
        }
      }
    );
  }

  public void testComplexSchemaValidation7_() throws Exception {
    doTest(
      new VirtualFile[]{
        findVirtualFile(getFullRelativeTestName()),
        findVirtualFile(getFullRelativeTestName(".xsd"))
      },
      false,
      false
    );
  }

  public void testWsdlValidation() throws Exception {
    doTest(getFullRelativeTestName(".wsdl"), false, false);
  }

  public void testErrorInDtd() throws Exception {
    String location = "def_xslt.dtd";
    String url = "http://www.w3.org/1999/XSL/Transform";
    ExternalResourceManagerExImpl.registerResourceTemporarily(url, location, getTestRootDisposable());
    doTest(new VirtualFile[]{findVirtualFile(getFullRelativeTestName()), findVirtualFile(BASE_PATH + location)}, false, false);
  }

  public void testResolveEntityUrl() throws Throwable {
    doTest(new VirtualFile[] {
      findVirtualFile(getFullRelativeTestName()),
      findVirtualFile(BASE_PATH + "entities.dtd")
    }, false, false);
  }

  public void testXsiSchemaLocation() throws Exception {
    doTest();
    doTest(getFullRelativeTestName("_2.xml"), false, false);
  }

  public void testXmlPsi() throws Exception {
    configureByFile(BASE_PATH + getTestName(false) + ".xml");

    final XmlTag root = ((XmlFile)myFile).getDocument().getRootTag();
    final XmlTag tag1 = root.getSubTags()[0];
    assert tag1.getLocalName().equals("foo");

    assertEquals("xxx" ,tag1.getAttributeValue("xxx", "abc"));

    final XmlTag tag2 = root.getSubTags()[0].getSubTags()[0];
    assert tag2.getLocalName().equals("bar");

    assertEquals("yyy", tag2.getAttributeValue("yyy", "abc"));
    assertEquals("zzz", tag2.getAttributeValue("zzz"));
    assertNull(tag2.getAttributeValue("zzz", "unknown"));
    assertNull(tag2.getAttributeValue("zzz", "abc"));

    final XmlTag tag3 = root.getSubTags()[0].getSubTags()[1];
    assert tag3.getLocalName().equals("bar2");
    assertEquals("zzz", tag3.getAttributeValue("zzz", "def"));
  }

  public void testXsiSchemaLocation2() throws Exception {
    doTest(
      new VirtualFile[]{
        findVirtualFile(getFullRelativeTestName()),
        findVirtualFile(BASE_PATH + getTestName(false) + "_1.xsd"),
        findVirtualFile(BASE_PATH + getTestName(false) + "_2.xsd"),
        findVirtualFile(BASE_PATH + getTestName(false) + "_3.xsd"),
      },
      false,
      false
    );

    XmlTag rootTag = ((XmlFile)myFile).getDocument().getRootTag();
    checkOneTagForSchemaAttribute(rootTag, "xmlns:test2", getTestName(false) + "_2.xsd");

    configureByFiles(null, findVirtualFile(BASE_PATH + getTestName(false) + "_2.xsd"),
                     findVirtualFile(BASE_PATH + getTestName(false) + "_3.xsd"));

    rootTag = ((XmlFile)myFile).getDocument().getRootTag();
    final List<XmlTag> tags = new ArrayList<>();

    XmlUtil.processXmlElements(
      rootTag,
      element -> {
        if (element instanceof XmlTag &&
