Searching through XML files with GTSpotter

Searching for and through XML files can be a common activity during certain kinds of software projects. Yet, the way we deal with these activities is typically rather cumbersome based on a combination of file search tools intertwined with editors.

In this post we show how a workflow like searching for XML files and then inside a target XML is seamlessly supported by GTSpotter.

First, finding an XML file is as easy as navigating through the file system. Let’s say we found our target XML.

File-search.png

The preview shows us the text. But, XML is not text. It’s XML. It has a clear structure, and we should be able to search in terms of that structure, too. Thus, diving into the XML file, exposes the internal structure of the XML directly in GTSpotter.

Inside-xml.png

In our case, the XML contains a catalog of books and related details. If we want to find for books, we can simply search and the elements are matched. Again, selecting an element offers a preview of the corresponding source to the right.

Search-xml.png

And this interface can be applied at any level within the XML document. For example, diving into a book allows us to quickly spot the author.

Dive-in-element.png

But, how difficult was it to implement this? Let’s take a look behind the scene.

Pharo already comes out of the box with extensions for navigating through FileReference instances. Thus, we only need to focus on the link between an XML file and a way to search inside the corresponding XMLDocument. To this end, we use the diving option of GTSpotter to go inside the FileReference, and we add a processor that uses the XMLDOMParser to parse the XML file and provide elements.

FileReference>>gtSpotterXMLDocumentFor: aStep
    <spotterOrder: 40>
    self extension = 'xml' ifFalse: [ ^ self ].
    aStep listProcessor
        title: 'XML document';
        allCandidates: [ (XMLDOMParser parse: self) allElements ];
        itemName: [ :element | element gtDisplayString ];
        filter: GTFilterSubstring;
        wantsToDisplayOnEmptyQuery: true

Similarly, we also extend the XMLNodeWithElements with a processor that can search throughout all elements.

XMLNodeWithElements>>gtSpotterAllElementsFor: aStep
    <spotterOrder: 40>
    aStep listProcessor
        title: 'All elements';
        allCandidates: [ self allElements ];
        itemName: [ :element | element gtDisplayString ];
        filter: GTFilterSubstring;
        wantsToDisplayOnEmptyQuery: true

The cherry on top comes in the shape of the preview. To offer the preview for an XML element, we another simple extension:

XMLNodeWithElements>>spotterPreviewSourceIn: aComposite
    <spotterPreview: 10>
    aComposite text
        title: 'Source';
        display: [ :anElement | self prettyPrinted ];
        entity: self

As these extensions are specific to the XML Parser project, they are naturally packaged in that project. This pattern is followed by other packages and it scales well.

That is all. With a very small investment, we provided a support for a custom use case. Even more interesting is that even though the interface is generic, it still matches quite naturally on our non-trivial use case.

GTSpotter can look deceptively simple, yet the potential is tremendous.

Posted by Tudor Girba at 12 July 2015, 7:15 pm with tags moose, tooling, gt link
|