PsiReferenceProvider doesn't work with Well Formed XML files

Answered

I implemented PsiReferenceProvider to navigate across XML files, but it works only if there is no correct declaration (correct !DOCTYPE) , though MyPsiReferenceProvider is triggered when I open the XML file even when I press CTRL + B it triggers PsiReferenceBase implementation. It calls methods like multiResolve() and resolve()

MyXmlReference extends PsiReferenceBase<PsiElement> implements PsiPolyVariantReference

the correct XML declaration is known as "Well Formed" XML

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE business PUBLIC "//Kas//DTD Business Data/" "busienss.dtd">
<business>
<WORK .../>
</business>

Same if XML definition looks like this (doesn't work)

<?xml version='1.0' encoding='UTF-8'?>
<business>
<WORK .../>
</business>

But, if XML looks like this

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE business "busienss.dtd">
<business>

the implementation of PsiReferenceProvider works as it was with correct XML declaration but in this case, it displays Usages.

How to make it works properly without touching the XML declaration, or without DOCTYPE tag as in the second example?

0
9 comments

Please try registering your provider with com.intellij.psi.PsiReferenceRegistrar#HIGHER_PRIORITY

0

No, it doesn't help, I tried HIGHER_PRIORITY and even 1000.0, it doesn't work when XML has no well-formed declaration

0

Sorry for delay. Could you please post full code of your com.intellij.psi.PsiReferenceRegistrar#registerReferenceProvider(com.intellij.patterns.ElementPattern<T>, com.intellij.psi.PsiReferenceProvider, double) implementation? Thanks.

 

0

Here you are

import com.intellij.psi.*
import com.intellij.psi.PsiReferenceRegistrar.HIGHER_PRIORITY
import com.intellij.psi.xml.XmlAttribute
import com.intellij.psi.xml.XmlTag
import com.intellij.util.ProcessingContext
import com.ytanikin.datasetnavigator.*


open class XmlPsiReferenceContributor : PsiReferenceContributor() {

override fun registerReferenceProviders(registrar: PsiReferenceRegistrar) {
registrar.registerReferenceProvider(ID_PATTERN, ID_REFERENCE_CONTRIBUTOR, HIGHER_PRIORITY)
registrar.registerReferenceProvider(ENTITY_PATTERN, ENTITY_REFERENCE_CONTRIBUTOR, HIGHER_PRIORITY)
registrar.registerReferenceProvider(FOREIGN_KEY_PATTERN, FOREIGN_KEY_REFERENCE_CONTRIBUTOR, HIGHER_PRIORITY)
}

private class IdPsiReferenceProvider : PsiReferenceProvider() {
override fun getReferencesByElement(element: PsiElement, context: ProcessingContext): Array<PsiReference> {
if (element !is XmlAttribute) return PsiReference.EMPTY_ARRAY
val id = element.value ?: return PsiReference.EMPTY_ARRAY
return arrayOf(XmlReference(element, findUsageTags(element, element.parent.name, id)))
}
}

private class EntityAttributePsiReferenceProvider : PsiReferenceProvider() {
override fun getReferencesByElement(element: PsiElement, context: ProcessingContext): Array<PsiReference> {
if (element !is XmlTag) return PsiReference.EMPTY_ARRAY
val id = element.getAttributeValue(ID_ATTRIBUTE) ?: return PsiReference.EMPTY_ARRAY
return arrayOf(XmlReference(element, findUsageTags(element, element.name, id)))
}
}

class ForeignKeyPsiReferenceProvider : PsiReferenceProvider() {
override fun getReferencesByElement(element: PsiElement, context: ProcessingContext): Array<PsiReference> {
if (element !is XmlAttribute) return PsiReference.EMPTY_ARRAY
val entityId = element.value ?: return PsiReference.EMPTY_ARRAY
val entityName = element.name.substringBefore(ID_POSTFIX)
return arrayOf(XmlReference(element, findDeclarations(element.project, entityName, entityId)))
}
}
}
0

Could you please clarify the *_PATTERN you use? It seems you're creating reference on the XmlAttribute, but not the XmlAttributeValue, is that intended?

0
companion object {
private const val DEBUG_METHOD_NAME = "withName"
private val DATASET_CONDITION = XmlPatterns.xmlTag().with(object : PatternCondition<XmlTag?>(DEBUG_METHOD_NAME) {
override fun accepts(xmlTag: XmlTag, context: ProcessingContext): Boolean {
return true
}
}).withParent(XmlPatterns.xmlTag().withName(DATASET_ROOT_TAG))

private val ID_PATTERN = XmlPatterns.xmlAttribute().withName(ID_ATTRIBUTE).withParent(DATASET_CONDITION)
private val ENTITY_PATTERN = XmlPatterns.xmlTag().withAnyAttribute(ID_ATTRIBUTE).withParent(XmlPatterns.xmlTag().withName(DATASET_ROOT_TAG))
private val FOREIGN_KEY_PATTERN = XmlPatterns.xmlAttribute().with(object : PatternCondition<XmlAttribute?>(DEBUG_METHOD_NAME) {
override fun accepts(xmlAttribute: XmlAttribute, context: ProcessingContext): Boolean {
return xmlAttribute.name.endsWith(ID_POSTFIX)
}
}).withParent(DATASET_CONDITION)
private val ID_REFERENCE_CONTRIBUTOR = IdPsiReferenceProvider()
private val ENTITY_REFERENCE_CONTRIBUTOR = EntityAttributePsiReferenceProvider()
private val FOREIGN_KEY_REFERENCE_CONTRIBUTOR = ForeignKeyPsiReferenceProvider()
}
0
class XmlReference(element: PsiElement, private val targets: List<PsiElement?>) :
PsiReferenceBase<PsiElement?>(element, TextRange(0, element.text.length + 3)), PsiPolyVariantReference {

override fun multiResolve(incompleteCode: Boolean): Array<ResolveResult> {
return targets.map { PsiElementResolveResult(it!!) }.toList().toTypedArray() }

override fun resolve(): PsiElement? {
return null
// val resolveResults = multiResolve(false)
// return if (resolveResults.isNotEmpty()) resolveResults[0].element else null
}

override fun getVariants(): Array<Any> {
return targets.stream()
.map { LookupElementBuilder.createWithSmartPointer(it!!.parent.text, it).withIcon(AllIcons.FileTypes.Java) }
.toArray()
}

override fun getCanonicalText(): String {
return "tesxt"
}

override fun resolveReference(): MutableCollection<out SymbolResolveResult> {

return super<PsiPolyVariantReference>.resolveReference()
}

override fun isSoft(): Boolean {
return true
}

override fun getValue(): String {
// val text = myElement!!.text
// val range = rangeInElement
return "asdfasdf"
}


}
0

It seems what you actually want to build is installing references on the "id" XML attribute, so you must register the reference on the XmlAttributeValue of "id" attribute, not on the parent containing XmlTag.

The pattern would look something like this:

XmlPatterns.xmlAttributeValue().withLocalName("id")
.withSuperParent(2, XmlPatterns.xmlTag().withLocalName("tagName").withNamespace("Your XML namespace ID"))

 

 

Depending on your plugin's usecase, also consider using XML DOM Api instead of low-level XML access https://plugins.jetbrains.com/docs/intellij/xml-dom-api.html

0

Please sign in to leave a comment.