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.
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.
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.
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.
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.