The request
A customer came to me and said:
“So I’ve got some XML files which hold a sort of DSL which I use to configure my application. There are elements in this DSL which have id attributes, and other elements which have ref attributes. As you might expect, within my DSL, the ref attributes are references to the id attributes.
Is there anything in CodeRush to help my navigate easily among these id and ref attributes where their values match?”
My plugin-sense started tingling….
I immediately thought of our awesome Tab to Next Reference feature, and figured we could probably extend it to work in this new scenario.
Create the project
File… New Project Choose Language (C# in this case) DXCore\Standard Plug-in Choose a name for your project (I chose CR_XMLNav)
The Coding
First I used a template to add some boilerplate code. (Bold text shows custom changes)
private void registerXMLRefSearch()
{
var XMLRefSearch = new DevExpress.CodeRush.Core.SearchProvider();
((System.ComponentModel.ISupportInitialize)(XMLRefSearch)).BeginInit();
XMLRefSearch.Description = "XMLRefSearch";
XMLRefSearch.ProviderName = "XMLRefSearch"; // Needs to be Unique
XMLRefSearch.Register = true;
XMLRefSearch.UseForNavigation = true;
XMLRefSearch.CheckAvailability += XMLRefSearch_CheckAvailability;
XMLRefSearch.SearchReferences += XMLRefSearch_SearchReferences;
((System.ComponentModel.ISupportInitialize)(XMLRefSearch)).EndInit();
}
private void XMLRefSearch_CheckAvailability(object sender, CheckSearchAvailabilityEventArgs ea)
{
}
private void XMLRefSearch_SearchReferences(Object Sender, DevExpress.CodeRush.Core.SearchEventArgs ea)
{
}
Then I fleshed out the CheckAvailability routine:
private void XMLRefSearch_CheckAvailability(object sender, CheckSearchAvailabilityEventArgs ea)
{
// Get Active LanguageElement
var Element = CodeRush.Documents.ActiveTextDocument.GetNodeAt(CodeRush.Caret.Line, CodeRush.Caret.Offset);
if (Element == null)
return;
if (!(Element is XmlAttribute))
return;
// Allow search to start if Attribute
ea.Available = new string[] { "id", "ref" }.Contains(((XmlAttribute)Element).Name);
}
Essentially this routine looks to see if the caret is on a LanguageElement of type XmlAttribute whose name matches either id or ref. If it finds that this is the case, our searchProvider is made available, and processing continues with the XMLRefSearch_SearchReferences routine.
private void XMLRefSearch_SearchReferences(Object Sender, DevExpress.CodeRush.Core.SearchEventArgs ea)
{
// Store Value of initial XmlAttribute
TextDocument activeDoc = CodeRush.Documents.ActiveTextDocument;
string StartValue = ((XmlAttribute)activeDoc.GetNodeAt(CodeRush.Caret.Line, CodeRush.Caret.Offset)).Value;
// Iterate LanguageElements in solution
SolutionElement activeSolution = CodeRush.Source.ActiveSolution;
foreach (ProjectElement project in activeSolution.AllProjects)
{
foreach (SourceFile sourceFile in project.AllFiles)
{
SourceFile activeFile = CodeRush.Source.ActiveSourceFile;
ElementEnumerable Enumerator = new ElementEnumerable(sourceFile, new XMLAttributeFilter(StartValue), true);
foreach (XmlAttribute CurrentAttribute in Enumerator
{
ea.AddRange(new FileSourceRange(CurrentAttribute.FileNode, CurrentAttribute.ValueRange));
}
}
}
}
This code iterates through your solution (projects files etc) looking for appropriate XmlAttributes. The key to this little piece of code is this line here:
ElementEnumerable Enumerator = new ElementEnumerable(sourceFile, new XMLAttributeFilter(StartValue), true);
This creates an object capable of enumerating through the code looking for things which match a very specific set of criteria. The specifics of what will match are handled by our last piece of code.
public class XMLAttributeFilter : IElementFilter
{
private readonly string _startValue;
public XMLAttributeFilter(string StartValue)
{
_startValue = StartValue;
}
public bool Apply(IElement element)
{
// Skip if null
if (element == null)
return false;
// Skip if not XmlAttribute
if (!(element is XmlAttribute))
return false;
XmlAttribute CurrentAttribute = (XmlAttribute)element;
// Skip attribute if it doesn't have the correct name.
if (CurrentAttribute.Name != "id" && CurrentAttribute.Name != "ref")
return false;
// Skip the attribute if it doesn't have the same value as start point.
if (CurrentAttribute.Value != _startValue)
return false;
return true;
}
public bool SkipChildren(IElement element)
{
return false;
}
}
Each LanguageElement is checked and passed over if it fails any single check.
The full version of this plugin is hosted up on GitHub.com for those who are interested.
Free DevExpress Products – Get Your Copy Today
The following free DevExpress product offers remain available. Should you have any questions about the free offers below, please submit a ticket via the
DevExpress Support Center at your convenience. We’ll be happy to follow-up.