Build your XML the LinqToXML style in native code – Part I.
A lot of you probably already know SimpleStorage. For those who don’t:
- http://www.cromis.net/blog/2008/07/simple-xml-based-storage/
- http://www.cromis.net/blog/2008/07/simple-xml-based-storage-part-ii/
- http://www.cromis.net/blog/2009/07/simple-xml-based-storage-part-iii/
- http://www.cromis.net/blog/2010/02/what-is-new-in-simplestorage/
I read a blog entry recently about LinqToXML and while I was exploring the possibilities that it has, I wondered. Can something like this be done with Delphi and native code? Now SimpleStorage already enables you to write XML in a very compact and efficient way, but it still lacks some elegance on the more native XML building approach. Ok there is a question why would you even want to write XML code inside the IDE editor? But sometimes it is viable, maybe just a wrapper XML shell for something that gets dynamically injected into it. Nevertheless I was intrigued if it can be done. Primož Gabrijelčič has done it and blogged about it:
- http://17slon.com/blogs/gabr/2009/04/fluent-xml-1.html
- http://17slon.com/blogs/gabr/2009/04/fluent-xml-2.html
It was a nice display what fluent interfaces approach can do, but I was not happy completely. I was aiming at code that would get rid of the “Here, Back…” functions to navigate in the document, because you don’t need them. You always build your document in one swoop and maybe access single elements latter. I know why they were used in that approach and that is why I think with fluent interfaces alone it is hard to achieve the wanted result. SimpleStorage uses an approach similar to that, but not to full extent. The second reason was I wanted to implement it using SimpleStorage, because my aim is to evolve it to be a great and all around tool for XML and data manipulation.
The goal I wanted to achieve was that the Delphi code written inside IDE would match the actual XML as closely as possible and with as little boilerplate code as possible. This way you get the benefit of seeing the resulting XML before it is even created. Without further delay let me show you the results.
First the XML we are trying to construct.
<Demografija> <Males> <Janez>Novak</Janez> <Darko>Gazvoda</Darko> <Mitja>Dežela</Mitja> <Tilen>Medved</Tilen> </Males> <Females> <Person Name="Marija">Gorenjska</Person> <Person Name="Tanja">Notranjska</Person> <Person Name="Maja">Primorska</Person> <Person Name="Tadeja">Pomurje</Person> <Person Name="Irma">Posočje</Person> <Person Name="Marija">Notranjska</Person> </Females> </Demografija>
And now the code that does it
procedure TfMain.btnExample2Click(Sender: TObject); var MyStorage: ISimpleStorage; begin MyStorage := CreateStorage('Demografija'); CreateBuilder(MyStorage).Construct( [ AddElement('Males', [ AddElement('Janez', 'Novak'), AddElement('Darko', 'Gazvoda'), AddElement('Mitja', 'Dežela'), AddElement('Tilen', 'Medved') ]), AddElement('Females', [ AddElement('Person', ['Name', 'Marija'], 'Gorenjska'), AddElement('Person', ['Name', 'Tanja'], 'Notranjska'), AddElement('Person', ['Name', 'Maja'], 'Primorska'), AddElement('Person', ['Name', 'Tadeja'], 'Pomurje'), AddElement('Person', ['Name', 'Irma'], 'Posočje'), AddElement('Person', ['Name', 'Marija'], 'Notranjska') ]) ]); end;
And for the second example let me show how to inject the actual dynamically generated XML into the given builder template. The target XML is like this
<Books> <English> <Science> <Mathematics Difficulty="High">5</Mathematics> <Physics Difficulty="Low">0</Physics> <Computers Difficulty="Medium">8</Computers> <Engineering Difficulty="Low">4</Engineering> <Electoronics Difficulty="High">8</Electoronics> <Biology> <Microbiology Difficulty="High">4</Microbiology> <Undefined Difficulty="Low">3</Undefined> </Biology> </Science> <Musical> <Classic>2</Classic> <Etno>2</Etno> <Pop>8</Pop> </Musical> </English> </Books>
And the code that does it looks like this
procedure TfMain.btnExample1Click(Sender: TObject); var MyStorage: ISimpleStorage; begin MyStorage := CreateStorage('Books'); CreateBuilder(MyStorage).Construct( [ AddElement('English', [ AddElement('Science', [ AddElement('Mathematics', ['Difficulty', 'High'], 5), AddElement('Physics', ['Difficulty', 'Low'], 0), AddElement('Computers', ['Difficulty', 'Medium'], 8), AddElement('Engineering', ['Difficulty', 'Low'], 4), AddElement('Electoronics', ['Difficulty', 'High'], 8), AddElement('Biology', [ AddElement('Microbiology', ['Difficulty', 'High'], 4), AddElement('Undefined', ['Difficulty', 'Low'], 3), Add( procedure(const Element: IElement) begin // add few elements here Element.Append('InsertedNode').AsInteger := 1; Element.Append('InsertedNode').AsInteger := 2; Element.Append('InsertedNode').AsInteger := 3; end ) ]) ]), AddElement('Musical', [ AddElement('Classic', 2), AddElement('Etno', 2), AddElement('Pop', 8) ]) ]) ]); end;
The result is easily readable dont’ you think? I would really like to hear your opinion on the code. Maybe you have a suggestion or idea, or you simply have a comment about how it all sucks
The code can already be downloaded from my download page, but be warned, it is still in early development and it will change. It only works in Delphi 2010 (should in 2009) opposed to other parts of SimpleStorage that work in Delphi 2006 and up. Also I use Variants for values for now and that will change. I will replace them with records and implicit operator overloads.