Rory Becker - DevExpress CodeRush Blog

October 2013 - Posts

  • Custom Navigation within XML files

    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.

    • In this way we pass a list of all valid items on to the Tab to Next Reference feature which then allows us to seamlessly tab through all the references.

    • CR_XMLNav

    • Summary
    • This sort of technique is, as you can see, very simple to apply. I’m sure you can think of many similar occasions when you’ve wished you could jump between related bits of configuration. Well now you have the ability to do just that.

    The full version of this plugin is hosted up on GitHub.com for those who are interested.

  • How to generate a Simple ToString() implementation with CodeRush templates

    A customer recently asked:

    Does CodeRush have a quick way to auto-implement ToString()?
    If it outputted each prop Name & value it would be great.

    Never one to shirk a challenge, I quickly set about finding the quickest way to allow a user to generate a ToString() method which
    would provide the requisite information.

    The approach I took this time, was that of Templates. Note: This task could certainly have been achieved with a plugin, but it’s
    quicker and arguably more efficient to use Templates in this case, so that’s the route I chose to take.

    For a generic primer on how to create CodeRush templates see my earlier post: CodeRush Templates - Creation Basics

    Structure

    The solution to this is to create 3 templates. An Iterating Template, An Item Template and a Delimiting Template

    • The Iterating Template: This is triggered by the user, and controls the iteration of the properties.
    • The Item Template: This represents the text that is emitted for each property encountered.
    • The Separation Template: This is used to determine text that sits between invocations of the item template.

    Specifics

    The Iterating Template

    As indicated this template is the one which is called directly by the user and as such should carry a sensible name. I’m not so great at naming so I chose to call mine ToStringPropertyMethod

    It will therefore be invoked whenever the user types ToStringPropertyMethod and hits the spacebar (or tab if you’re in Friction free mode)

    The body of this template is:

    -------------------------------------------------------------

    public override string ToString()
    {
    return «ForEach(Property,PropertyEach,,PropertySeparator)»;
    }

    -------------------------------------------------------------

    The basic structure of this will look familiar. The signature is that of the standard ToString() override, and the braces are those present in any method in order to denote the start and end of that method.


    What will be less familiar is the code within the chevrons (« and »)

    This code is a TextCommand which is interpreted by CodeRush, rather than emitted directly into your code.

    This TextCommand is called 'ForEach' and it takes up to 5 parameters. In this instance we only need 3 of these.

    • 'Property' specifies that we are interested in iterating through any properties of the current class.
    • 'PropertyEach' is the name of the template to expand for each property found.
    • 'PropertySeparator' is the name of the template to be expanded between any 2 calls to PropertyEach

    The other 2 params are unused in this case but control templates expanded at the start and end of the sequence.

    As you can see, this template depends on the existance of both PropertyEach and PropertySeperator.

    The expansion of the PropertySeperator Template is predictably a simple + sequence thus:

    -------------------------------------------------------------

     +

    -------------------------------------------------------------


    The content of the PropertyEach template is a little more complicated:

    -------------------------------------------------------------

    "«?Get(itemName)»: " + «?Get(itemName)».ToString()

    -------------------------------------------------------------

    This represets the building of some code which will itself emit a string representative of the property being iterated.

    The bit you may be less familiar with is «?Get(itemName)». It is a StringProvider which provides access to strings setup by previous uses of the «?Set» StringProvider.


    In this case, each time the «?ForEach» StringProvider  finds a new Property, it sets itemName to the name of that Property. This gives us a handy way to access that information and emit it into out code.

    If you put all of this together and Expand the ToStringPropertyMethod template from within the following class...

    -------------------------------------------------------------

        public class Example
        {
            public string PropertyName1 { get; set; }
            public string PropertyName2 { get; set; }
            public string PropertyName3 { get; set; }
        }
    -------------------------------------------------------------

    ...you get the following result...

    -------------------------------------------------------------

        public class Example
        {
            public string PropertyName1 { get; set; }
            public string PropertyName2 { get; set; }
            public override string ToString()
            {
                return "PropertyName1: " + PropertyName1.ToString() + "PropertyName2: " + PropertyName2.ToString();
            }

        }
    -------------------------------------------------------------


    Summary

    As you can see, this code will emit the name and value of each property of the class when called.

    Obviously the formatting could be improved, but I think you can see the potential demonstrated here.

    What about generating XML or JSON based strings.

    These, of course, are left as an exercise for the reader. :)





LIVE CHAT

Chat is one of the many ways you can contact members of the DevExpress Team.
We are available Monday-Friday between 7:30am and 4:30pm Pacific Time.

If you need additional product information, write to us at info@devexpress.com or call us at +1 (818) 844-3383

FOLLOW US

DevExpress engineers feature-complete Presentation Controls, IDE Productivity Tools, Business Application Frameworks, and Reporting Systems for Visual Studio, along with high-performance HTML JS Mobile Frameworks for developers targeting iOS, Android and Windows Phone. Whether using WPF, ASP.NET, WinForms, HTML5 or Windows 10, DevExpress tools help you build and deliver your best in the shortest time possible.

Copyright © 1998-2017 Developer Express Inc.
All trademarks or registered trademarks are property of their respective owners