What is new in SimpleStorage
As you probably know by now (or not) SimpleStorage is a set of interfaces to ease the manipulation of XML documents. It is based on OmniXML, a wonderful Delphi XML parser. Simple Storage aims to treat XML as a practical data storage and means of data serialization and transportation. I have written about its usage in a series of articles, some time ago. Here are the articles:
- Simple XML based storage – Part I.
- Simple XML based storage – Part II.
- Simple XML based storage – Part III.
From then, the framework has matured and I use it in a lot of my software. It has proven invaluable in terms of time savings and ease of XML manipulation. After all I have written it because I was tired of doing the same XML tasks over and over again. Recently, I upgraded it with two new important units . They cover two aspects of XML manipulation:
- Encryption on a document scale
- Working with collection of documents
I also did an upgrade of the core IElement interface. The goal was easier node attributes manipulation. I will show examples of all three improvements here. The new version of SimpleStorage can be found on my download page as always.
Encryption on a document scale
I recently had the need to encrypt whole XML documents. Since I already have SimpleStorage and XTEA encryption algorithm in my library, this was a no brainer. Whole documents can now be encrypted with some very simple code:
EncryptedStorage('MyVeryStrongKey').SaveToFile(StorageFromFile(OpenDialog.FileName), SaveDialog.FileName);
And decrypted just as easily.
EncryptedStorage('MyVeryStrongKey').LoadFromFile(OpenDialog.FileName).SaveToFile(SaveDialog.FileName);
I have intentionally wrote this in one line, because I always want to show how compact and powerfull the whole framework is. It tends to minimize tedious code and make your life easier. In real apps this should probably be two lines for easy redability.
Storage := StorageFromFile(OpenDialog.FileName); EncryptedStorage('MyVeryStrongKey').SaveToFile(Storage , SaveDialog.FileName);
You can also encrypt / decrypt directly but you can look for that yourself. The code is obvious.
Easier node attributes manipulation
As I said, I strive to make the code compact and clear and attribute manipulation was one thing, that bugged me from the beginning of SimpleStorage. Everything else was very compact, but if you wanted to manipulate attribute “Language” of node “Book”, for instance, you had to write something like the following code. The first line is setting “Language” attribute of the “Book” node, by first getting the “Book” node and the second is setting the “Language” directly from “Book” node.
CurrentNode.Get('Book').Attributes.Ensure('Language').AsString := 'English'; CurrentNode.Attributes.Ensure('Language').AsString := 'English';
Which is ok, but it stand out from other SimpleStorage code. So I added “shortcuts” to the IElement interface. Now you can do the same like this:
CurrentNode.EnsureAttr('Book', 'Language').AsString := 'English'; CurrentNode.EnsureAttr('Language').AsString := 'English';
It saves a lot of characters on the long run
Working with collection of documents
Until now, SimpleStorage was great if you wanted to manipulate a single XML document. But what if you have lots of those and you wanted to process all of them. You had to write your own code (which is easy of course, but again repetitive work) and work with each document separately. Well no more. You now have “IDocuments“ interface that enables you to work over collections of XML documents. I will show this on a simple example. This was a real task, I had in front of me a few days ago. I had a bunch of XML documents looking like this:
<Devices> <Device ID="002694350026014400780050BF3F5173" InBound="23632" OutBound="13092" /> <Device ID="002694350026012900880050BF3F5173" InBound="14728" OutBound="41084" /> <Device ID="002694359925017200280050BF3F5173" InBound="22116" OutBound="61808" /> <Device ID="002694350026013800980050BF3F5173" InBound="15140" OutBound="54612" /> <Device ID="002694359725014400580050BF3F5173" InBound="13460" OutBound="34064" /> <Device ID="002694359925013100880050BF3F5173" InBound="16332" OutBound="47120" /> <Device ID="002694359625013600380050BF3F5173" InBound="14860" OutBound="47752" /> <Device ID="002694359625014900680050BF3F5173" InBound="6120" OutBound="10852" /> <Device ID="002694359925016600480050BF3F5173" InBound="11724" OutBound="46016" /> <Device ID="002694350026011900980050BF3F5173" InBound="6404" OutBound="13540" /> <Device ID="002694359925013600780050BF3F5173" InBound="7616" OutBound="10376" /> <Device ID="002694359525017200080050BF3F5173" InBound="3060" OutBound="9996" /> <Device ID="002694359925012400380050BF3F5173" InBound="460" OutBound="34084" /> <Device ID="002694359725010700280050BF3F5173" InBound="8228" OutBound="35664" /> <Device ID="002694350026012000780050BF3F5173" InBound="4076" OutBound="26664" /> </Devices>
This is a shortened version, but you get the idea. It is an XML that holds the “InBound” and “OutBound” traffic for mobile devices, over the HTTP protocol. In short, it enables me to see how much traffic my application generates for a single device. The shown XML, is a log for a single month of traffic. And in over two years, quite a few of these files accumulated. It would be easy to make a sum of “InBound” and “OutBound” traffic and report that for a single device over the complete time span in a traditional way. But lets see how we can do that with SimpleStorage. First let me show how to get all log entries for a single device. All XML logs were stored in “s:\bandwidth” directory.
procedure TForm1.Button2Click(Sender: TObject); var Documents: IDocuments; begin Documents := CreateCollection('s:\bandwidth', False); Documents.Get('//Device[@ID="3FBF5000735108010E1212674B5D036A"]', procedure(const Document: string; const Element: IElement; const Data: Pointer) begin ListBox1.Items.Add(Document + ' - ' + Element.Attributes.Get('InBound').AsString); end ); end;
Thats it. Simple isn’t it. Try to do the same with classic MS XML and compare the result. Let me tell you again what I just did in these few lines of code. First I acuired all XML documents in a sinlge directory. Then I enumerated all the documents, but only processed the ones that have a distinct node (based on XPath) in them. Finally I printed some results for the selected nodes. If you dislike the anonymous approach you can also do it with classic callback functions. The interface supports both. the Get function / procedure is overloaded and it returns documents collection or it enumerates nodes over all documents based on XPath.
If you want to filter the collection of documents you have at your disposal you can do it like I have shown in this next example. This is just an example to show you how, it has no real value. In the callback you decide if the document will be included in the result collection or not. Then the code enumerates all documents and prints out a single attribute.
procedure TForm1.Button3Click(Sender: TObject); var Documents: IDocuments; Document: ISimpleStorage; Element: IElement; Subset: IDocuments; begin ListBox1.Clear; Randomize; Documents := CreateCollection('s:\bandwidth', False); Subset := Documents.Get('//Device[@ID="3FBF5000735108010E1212674B5D036A"]', procedure(const Document: ISimpleStorage; const FileName: string; var Include: Boolean; const Data: Pointer) begin Include := Random(10) > 5; end ); for Document in Subset do begin Element := Document.Get('//Device[@ID="3FBF5000735108010E1212674B5D036A"]'); ListBox1.Items.Add(Element.GetAttr('InBound').AsString); end; end;
Now finally let me show the real code that helped me sumarize the traffic report. I used a classic callback here for sake of redability.
type TDeviceObject = class InBound: Int64; OutBound: Int64; end; procedure TForm1.OnDeviceCallback(const Document: string; const Element: IElement; const Data: Pointer); var DeviceIdx: Integer; HashTable: TStringList; DeviceObject: TDeviceObject; begin HashTable := TStringList(Data); DeviceIdx := HashTable.IndexOf(Element.GetAttr('ID').AsString); if DeviceIdx = -1 then DeviceIdx := HashTable.AddObject(Element.GetAttr('ID').AsString, TDeviceObject.Create); DeviceObject := TDeviceObject(HashTable.Objects[DeviceIdx]); DeviceObject.InBound := DeviceObject.InBound + Element.GetAttr('InBound').AsInteger; DeviceObject.OutBound := DeviceObject.OutBound + Element.GetAttr('OutBound').AsInteger; end; procedure TForm1.Button1Click(Sender: TObject); var I: Integer; HashTable: TStringList; Documents: IDocuments; OutputData: TStringList; DeviceObject: TDeviceObject; begin HashTable := TStringList.Create; try Documents := CreateCollection('s:\bandwidth', False); Documents.Get('//Device', OnDeviceCallback, HashTable); OutputData := TStringList.Create; try for I := 0 to HashTable.Count - 1 do begin DeviceObject := TDeviceObject(HashTable.Objects[I]); OutputData.Add(Format('%s,%d,%d', [HashTable[I], DeviceObject.InBound, DeviceObject.OutBound])); end; OutputData.SaveToFile('S:\bandwidth\Bandwidth.txt'); finally OutputData.Free; end; finally HashTable.Free; end; end;
The result is a CSV file ready for excel.
Kryvich wrote,
Vote for an inclusion it to the RTL, as a default XML library!
Link | February 8th, 2010 at 8:31 am
Iztok Kacin wrote,
Yes that would be nice. Anyway I plan to further develop the framework. There is still a lot to do.
Link | February 8th, 2010 at 10:49 am
Iztok Kacin wrote,
I got an email from someone about compilation problems. I tried to respond to the given e-mail address, but the address is not valid. If you are reding this, please send me you e-mail again so I can respond please.
Otherwise I tried to compile the demo with the latest OmniXML and code on my blog and it compiles fine. There was and old chunk of code in the demo. I removed that and it compiles ok now. If you still have trouble compiling drom me and e-mail.
Link | February 8th, 2010 at 10:52 am
Iztok Kacin wrote,
And I will add more demos soon. I promise. I just didn’t have time to do it yet. But there is some sample code in my blog articles, so it should not be to hard to use it. If you still have questions I would be glad to answer them.
Link | February 8th, 2010 at 10:55 am
MarkF wrote,
This looks very impressive. Nice job!
Link | February 8th, 2010 at 4:56 pm
Iztok Kacin wrote,
@MarkF
Thanks. I hope it will be useful for the people who want to use it.
Link | February 8th, 2010 at 5:40 pm
From Zero To One » Blog Archive » Build your XML the LinqToXML style in native code – Part I. wrote,
[...] http://www.cromis.net/blog/2010/02/what-is-new-in-simplestorage/ [...]
Link | March 9th, 2010 at 10:12 am