Introducing DFMJSON, the DFM parser and scriptable bulk editor
Yesterday at work, while tracking down a graphical glitch, I found that TPanel objects on many, many dialog boxes in our system (over 300 of them) had a certain property set incorrectly. There are basically three ways to fix something like that:
- A loooooooong slog doing it all manually. This can take days, and you better hope you don’t miss anything…
- Add some sort of hack at runtime that sets the property. This means that the DFM and the values seen in the form designer no longer reflect the initial state of the form when it’s presented to the user, which is not a good thing.
- Find a way to automate the process.
I’ve been finding various scenarios at work where parsing a DFM into an abstract syntax tree and running transformations on it, then saving back to DFM format, would be advantageous. My boss has always felt that the effort to do that outweighs the benefits, but in this case, the problem was big enough that he agreed it would be worth pursuing, particularly after I assured him I could have this working in one day.
Well, right as it was getting to time to leave, I showed him the results. It worked, the bug was fixed, the boss was happy… and I asked him if it would be OK to publish this because it can be generally useful, and he said yes.
I called it DFMJSON, which is exactly what it sounds like: a library that converts DFMs into JSON data and back again. It uses parsing logic based heavily on the System.Classes.pas parser to build an abstract syntax tree of a DFM file, containing all the data needed to recreate the original DFM.
The second part of the work was grafting the DWS scripting engine onto it. DWS has an extension (dwsJSONConnector) that allows scripts to work with JSON data natively. By coding up a simple tool that takes the JSON objects produced by DFMJSON and feeds them into a DWS script, then saves the object back to DFM format when it’s done, I built a batch processor that took about 5 seconds to work its way through every form file in our project.
I’ve posted the project over at https://github.com/masonwheeler/DFMJSON for anyone to look at and play around with. It includes a “RTL” folder where the script engine will automatically look to resolve uses clauses. It doesn’t contain much yet, just a couple utility functions to finds all descendant controls, or all descendant controls of a certain class (used to retrieve all instances of TPanel for each form), but more could easily be added.
Unfortunately, there are two very useful things that the underlying dwsJSON library allows but are not exposed by dwsJSONConnector: getting the parent of a JSON value, and detaching a value from its parent. I think I’ll add support for those sometime soon. The second in particular would make it trivial to use DfmJsonProcessor to instantly strip all those obnoxious Explicit* properties out of your DFMs, just to name one possible use.
NOTE: This was written in Delphi XE4, and it makes use of helpers on primitive types, so it won’t build in any version that doesn’t have those. (When were they implemented? XE3? XE4? I’m pretty sure they weren’t there for XE2.) Also, to build DfmJsonProcessor properly in the IDE, you’ll need DWS at revision 2600 (which I just checked in) or later, since it adds the IDE registration for dwsJSONConnector.
Anyway, check it out and let me know what you think. Any feedback (or patches!) will be welcome.
Record helpers are XE3
Great work Mason. Thanks for sharing. I’ll be sure to pick it up if/when I get back into UI development.
You could also use the Property Corrector wizard in CnPack, which works great for bulk changing properties on components. Available from http://cnpack.org
If anybody wants another option, I created a Go library that can parse, change and generate DFM files: https://github.com/gonutz/dfm
Hello Mason,
Eric helped me to get a working version of DWScript for Delphi 11.1
I tried your DFMJSON but, alas, it did not work.
Some errors.
I can send you the two screenshots later after you eMail be back.
cheers
Richard C
NSW
Australia
Do you have any example scripts to show how dfmJSON works ?
Does it pull/extract/parse SQL strings out of my DELPHI Project component- ADOQuery.SQLstrings?
Any help is appreciated.
Eric Grange(DWScripts) kindly gave me code updates to make dfmJSON operational/compileable in Delphi 11.1.