Simple XML based storage – Part II.
Last time I showed some basic data manipulation with the storage and explained the very basics of its workings. Ok it was maybe interesting, but it was nothing to be excited about. So this time I am going to show you some advanced data manipulation tehniques and explain some other things about SimpleStorage.
First of all we have three ways of creating the storage.
1 2 3 4 5 | function CreateStorage(const RootNode: XmlString = ''): ISimpleStorage; function StorageFromXML(const XML: XmlString): ISimpleStorage; function StorageFromFile(const FileName: XmlString): ISimpleStorage; function StorageFromStream(const Stream: TStream): ISimpleStorage; function StorageFromElement(const Element: IElement): ISimpleStorage; |
The idea behind these is that it lets us create the storage in one step. We do not need to create the storage and load it from file (yes we can do that if we want to) if we already know that we want to create a storage based on existing XML file (like conf file for instance). Same goes for XML as WideString, or for new in memory storage that lets us declare the root element right in the constructor.
Now let me show you some examples of getting the values from the storage.
1 2 3 4 | SourceStorage.Get('English/Science/Mathematics').AsInteger; SourceStorage.Get('English/Science/DoesNotExist').AsInteger; SourceStorage.Get('English/Science/DoesNotExist').AsIntegerDef; SourceStorage.Get('English/Science/DoesNotExist').AsIntegerDef(10); |
Here we have four ways of getting the values. The first one we already saw last time and it gets us the number of Mathematical books as integer. Get always tries to get the node, but if the node is not there it stil returns a valid interface. But the interface has an internal pointer to actual XML node which is nil.So the second example will stil generate AV. This is welcome in certain conditions, when we want to be aware of missing values, but most of the time, we want the code to always succed. So the last two examples do not generate AV. Instead they return the default value, which is “0″ as default for integer and “10″ in the last example. Here we explicitly demanded “10″ if the value is not found.
We can also use the power of XPath in our searches.
1 2 3 4 | SourceStorage.Get('English/Science/*[@Difficulty="Medium"]').AsInteger; SourceStorage.Get('English/Science/[@Difficulty]').AsInteger; SourceStorage.Ensure('English/Science/Astronomy[@Difficulty="High"]').AsInteger; |
The first example will return the first value under the science books that has attribute Difficulty=”Medium” and the second one will return the first value that has attribute Difficulty present. The third example is more interesting because it check if an element with name Astronomy and attribut Difficulty=”High” exists. And if it does not then it created such element (attribute included).
And we can read or write the values in many different formats (types). Here is the IValue interface that shows all the possibilities.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | IValue = Interface(IInterface) ['{3D25DFB7-CC19-4598-9010-8FA55322F126}'] // getters and setters function _GetName: XmlString; function _GetAsFloat: Real; function _GetAsBinary: IBinary; function _GetAsString: XmlString; function _GetAsInteger: Integer; function _GetAsBoolean: Boolean; function _GetAsDateTime: TDateTime; procedure _SetAsFloat(Value: Real); procedure _SetAsString(Value: XmlString); procedure _SetAsInteger(Value: Integer); procedure _SetAsBoolean(Value: Boolean); procedure _SetAsDateTime(Value: TDateTime); // value properties that get/set value in different formats function AsFloatDef(const DefValue: Real = 0): Real; function AsStringDef(const DefValue: XmlString = ''): XmlString; function AsIntegerDef(const DefValue: Integer = 0): Integer; function AsBooleanDef(const DefValue: Boolean = False): Boolean; function AsDateTimeDef(const DefValue: TDateTime = 0): TDateTime; property AsDateTime: TDateTime read _GetAsDateTime write _SetAsDateTime; property AsBoolean: Boolean read _GetAsBoolean write _SetAsBoolean; property AsInteger: Integer read _GetAsInteger write _SetAsInteger; property AsString: XmlString read _GetAsString write _SetAsString; property AsFloat: Real read _GetAsFloat write _SetAsFloat; property AsBinary: IBinary read _GetAsBinary; property Name: XmlString read _GetName; end; |
Ok now let me show how we can enumerate certain values or nodes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | for Element in SourceStorage.Get('English/Science').Values do mmResultsData.Lines.Add(Format('%s : %s', [Element.Name, Element.AsString])); for Element in SourceStorage.Get('English/Science').Nodes do mmResultsData.Lines.Add(Format('%s', [Element.Name])); for Element in SourceStorage.Get('English/Science').Values('*[@Difficulty="High"]') do mmResultsData.Lines.Add(Format('%s : %s', [Element.Name, Element.AsString])); for Element in SourceStorage.Values('English/Science/*[@Difficulty="High"]') do mmResultsData.Lines.Add(Format('%s : %s', [Element.Name, Element.AsString])); for Element in SourceStorage.Get('English/Science').Values('Engineering') do mmResultsData.Lines.Add(Format('%s : %s', [Element.Name, Element.AsString])); |
I will not talk to much about this as you probably see what the results will be by now. I will only explain the last one. The last example enumerates all values which are named ‘Engineering’ under the ‘English/Science’ node.
Maybe it is also worth noting that the first example iterates on all values (elements that have no children) of a single node and the secod example iterated on all nodes (elements that have childred). It is similar to folders and files if we use filesystem as comparison.
Probably one of the strongest features is data merging. You can merge data from different storages with almost no code at all, or you can even copy data inside the same storage with ease. Two examples to demostrate this. We have two different XML samples. The Target storage.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <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> </Science> <Musical> <Classic>2</Classic> <Etno>2</Etno> <Pop>8</Pop> </Musical> </English> </Books> |
And the source storage.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <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> |
Now we have these lines of code.
1 2 3 4 5 | SourceStorage := CreateStorageFile(FRootDir + DIR_DATA + FILE_DATA_TWO); TargetStorage := CreateStorageFile(FRootDir + DIR_DATA + FILE_DATA_ONE); TargetStorage.Get('English').Append(SourceStorage, True); TargetStorage.Get('English/Musical').Append(SourceStorage.Get('Females').Values); |
And the result of these four lines of code (of which only two line actually manipulate the data) is shown bellow. The first merge appended the whole source storage to the single target node (‘English’) in the target storage. And the second merge appended all values that were result of a single iteration to another target single node (‘English/Musical’).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | <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> </Science> <Musical> <Classic>2</Classic> <Etno>2</Etno> <Pop>8</Pop> <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> </Musical> <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> </English> </Books> |
I hope you see how powerfull this data manipulation was
Ok that is all in the second part of the series about the SimpleStorage. Next time I will conclude it with some final thoughts and some more ways of using it.
The code is considered to be stable. But there may still be some bugs in it. To download the code with the demo get it here – SimpleStorage
From Zero To One » Blog Archive » What is new in SimpleStorage wrote,
[...] Simple XML based storage – Part II. [...]
Link | February 7th, 2010 at 2:49 pm
From Zero To One » Blog Archive » Build your XML the LinqToXML style in native code – Part I. wrote,
[...] http://www.cromis.net/blog/2008/07/simple-xml-based-storage-part-ii/ [...]
Link | March 9th, 2010 at 10:12 am