Finding PsiReference to an XML file referenced by an XML tag value in custom PsiReferenceContributor

Answered

Hello!

I am attempting to create a custom PsiReferenceContributor which interprets the value of a tag in an XML file as a reference to another XML file. For example:

<site>
<group-profile-ref>mygroup</group-profile-ref>
</site>

where mygroup is interpreted as a reference to an xml file that lives in the same root as the above xml file and would have a relative path of something like m/mygroup/config/config.xml

I have been able to get a VirtualFile for the mygroup config.xml file, but am having no luck getting a reference to the that file so that  "Go To Declaration" works when mygroup is clicked on.

Here is what I have so far:

public class SiteReferencesContributor
extends PsiReferenceContributor {
private static final VirtualFilePattern CONFIG_XML = namedXmlFile("config.xml");
private static final XmlTagPattern.Capture SITE_TAG = xmlTag().withName("site");

@Override
public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) {
registrar.registerReferenceProvider(psiElement(XML_DATA_CHARACTERS)
.withParent(xmlText()
.withParent(siteChildren("profile-ref", "group-profile-ref",
"oem-profile-ref", "country-profile-ref"))),
new ProfileReferenceProvider());
}

private static VirtualFilePattern namedXmlFile(final String name) {
return virtualFile()
.ofType(XmlFileType.INSTANCE)
.withName(name);
}

private static PsiElementPattern<XmlTag, XmlTagPattern.Capture> siteChildren(String... names) {
return xmlTag()
.withName(names)
.withParent(SITE_TAG)
.inVirtualFile(CONFIG_XML);
}
}
public class ProfileReferenceProvider
extends PsiReferenceProvider {
private static final PsiFileReferenceHelper fileReferenceHelper = new PsiFileReferenceHelper();

@NotNull
@Override
public PsiReference[] getReferencesByElement(@NotNull final PsiElement element, @NotNull ProcessingContext context) {
String profile = element.getText();
final Project project = element.getProject();
final VirtualFile referringFile = element.getContainingFile().getVirtualFile();
final PsiFileSystemItem dvsRoot = fileReferenceHelper.findRoot(project, referringFile);
final String relativeProfilePath = profile.toLowerCase().charAt(0) + "/" + profile + "/config/config.xml";
final VirtualFile profileXml = dvsRoot.getVirtualFile().findFileByRelativePath(relativeProfilePath);
if (profileXml != null && profileXml.exists()) {
final PsiManager psiManager = dvsRoot.getManager();
PsiFile file = psiManager.findFile(profileXml);
return new PsiReference[]{file.getReference()};
}
return PsiReference.EMPTY_ARRAY;
}
}

Any help would be greatly appreciated.

Thanks!

 

 

3 comments
Comment actions Permalink

com.intellij.psi.impl.source.PsiFileImpl#getReference returns null always, try returning com.intellij.psi.PsiReferenceBase#createSelfReference(T, com.intellij.psi.PsiElement) with resolveTo=file

 

You might want to consider using XML DOM API if you have a more complex model/functionality in mind https://www.jetbrains.org/intellij/sdk/docs/reference_guide/frameworks_and_external_apis/xml_dom_api.html

1
Comment actions Permalink

Excellent, that worked! Yann, thank you for your assistance.

 

We have our own in-house repository where these configuration files are stored and a CLI to fetch them. In the case where the developer does not have the referenced xml configuration file checked out, i would like to offer the option to check out the file by executing the CLI command before going to the declaration.

Is there a way to achieve this so that when the user clicks on the declaration that doesn't exist, the option to check out that file is presented?

Thank you again!

1
Comment actions Permalink

You can provide an inspection that checks references and highlights unresolved ones and provides quickfix via Alt+Enter.

0

Please sign in to leave a comment.