Drive the TAXI!






4.67/5 (15 votes)
A new face for XmlDocument
Please leave your comments.. Is that useful? If not, why? Does it need to be improved? If yes, how?
TAXI - Tolgahan ALBAYRAK's XML Implementation
Introduction
A few days ago, when i was developing an app on Adobe Flex, i thought myself why .Net's XmlDocument is not easy like E4X. I know, LINQ makes .Net life easier, but what about computers or hostings which not support .Net Framework 3.5? And in my opinion, LINQ could be difficult for beginner programmers or others who has not much experience..
Anyway, two days ago when i was working on .Net i need some sort operations on XmlDocument. I wrote a new class and i shared in Code Project also. (TAXmlDocument)
It was ok for sorting, but for the other operations writing same lines always.. arrrgghh.. It takes a lot of time..
So, there must be some libraries to make xml life easier.. I searched on internet and i discovered a really good xml library for .Net : Mvp.Xml Project
It was perfect, but it is not the same that i seek. So, i started to my project, TAXI.
Firstly I imagined E4X in Adobe Flex.. On E4X, we can do inline check for anything. for example, if I want to select the child nodes whose id attribute value is 5, the query should be:
xml_instance.*(@id == 5).*;
TAXI should be easy like that.. So, I thought my necessities and began to write.. Here is the result.
Using the code
I think, a HOW TO dialog will be better to explain how TAXI is easy. So, there are a student and a teacher on following dialog.. Student asks, teacher answers.. (Both of them are me :) )
Student : Why this article's name is : Drive the TAXI.. I understood TAXI but what about "Drive" ?
Teacher : Well, This is not a joke. TAXI is based on XmlDocument. And, to enter TAXI's world you should call "drive" property. drive property returns as TAXI.Selection.. TAXI.Selection is base of everything. for example :
TAXI xml = new TAXI(); TAXI.Selection selection = xml.drive;
Student : Ok. If i want to select root node what should I do?
Teacher : The first call of drive returns as root node.
Student : Ok, I want to select children of root node.. how?
Teacher : Just call "child" property of current selection. the "child" property returns as Selection also. Actually all properties and methods except information properties return as Selection.
TAXI.Selection selection = xml.drive.child;
Student : Ok, How can I select the all following children (child, grandchild.. etc) ?
Teacher : Just call "any" property.. That's you want..
TAXI.Selection selection = xml.drive.any;
Student : Here is my XML File.. So I will ask my next questions about this file..
<library>
<book a='123' genre='novel' ISBN='1-861001-57-5' date='1/1/1980 01:01:00'>
<title>Pride And Prejudice</title>
</book>
<book genre='novel2' ISBN='1-861001-57-5' date='4/1/1980 01:01:00'>
<title>Pride And Prejudice 222</title>
</book>
<book genre='novel3' ISBN='1-861001-57-51' date='1/2/1980 01:01:00'>
<title>Pride And Prejudice</title>
</book>
<book genre='sun' ISBN='1-81920-21-2' date='1/1/1979 01:01:00'>
<title>Hook</title>
<node>
<node2>
<node a='novel2'></node>
<node b='novel'></node>
</node2>
</node>
</book>
</library>
Student : Ok, I want to select first node of library.. how?
Teacher : just call "first" property.. Also you can call "next", "nexts", "prev", "prevs", "parent", "parents", "parentAndMe", "parentsAndMe", "child", "all", "any", "last" for other operations..
TAXI.Selection selection; //Returns first node of selection selection = xml.drive.first; //Returns next sibling of selection selection = xml.drive.next; //Returns all next siblings of selection selection = xml.drive.nexts; //Returns previous sibling of selection selection = xml.drive.prev; //Returns all previous siblings of selection selection = xml.drive.prevs; //Returns parent of selection selection = xml.drive.parent; //Returns parent and current; selection = xml.drive.parentAndMe; //Returns all parents (parent, grandparent) selection = xml.drive.parents; //Returns all parents and current selection = xml.drive.parentsAndMe; //Returns children selection = xml.drive.child; //Returns all after and current selection = xml.drive.all; //Returns all after selection = xml.drive.any; //Returns last child of current selection = xml.drive.last;
Student : Ok, I want to read "ISBN
" attribute's value of the node which "genre
" attribute is "sun
".. how ?
Teacher : "of" function runs query.. so run a query and look at value..
//The "of" method search on current nodes' children TAXI.Selection selection = xml.drive .of(Part.AttrValue["genre"] == "sun") .attr("ISBN"); MessageBox.Show(selection.value);
In this example we see "of" method.. the "of" method returns as TAXI.Selection too. it selects the child nodes of current node.
TAXI.Selection.of(params string[] names) --- Selects the nodes with given name.
TAXI.Selection.of(Query) --- Selects the nodes matches with Query;
for example :
Student : I want to select "book
" and "node
" nodes. How?
Teacher : Use "of" method.
TAXI.Selection selection = xml.drive .all .of("book", "node"); MessageBox.Show(string.Join(",", selection.names));
The Part Class For TAXI queries..
Part class is an abstract class.. This class allows us to create TAXI queries easily..
Part.AttrValue[attributeName] -- This will help us to check Attribute values.
Part.AttrName[attributeName] -- This will help us to check Attribute names.
Part.Name --- This will help us to check Node names.
Part.Value --- This will help us to check Node values.
Working with TAXI Queries
TAXI comes with a query class what name is Query. Query allows us to select any data what we need.. Query class' "|
" operator is overrided. So, we can use this operator "OR
" to select both sides. For example :
Query a, b, c; a = b | c; // this represents us "a selects both b and c";
Part class' "|
", "&
", "+
","-
","*
","/
","==
","!=
",">
","<
",">=
","<=
" operators are overrided. We can use this operators to compare data. This operators allows to use any object or array in right side. And the comparison try to convert the current data to given type. Then starts comparison. For example :
Student : I want to select the books which date attribute less than "1/1/1980
". how?
Teacher : just give a date value to attribute query..
//The "of" method search on current nodes' children TAXI.Selection selection = xml.drive .of(Part.AttrValue["date"] < DateTime.Parse("1/1/1980")); MessageBox.Show(selection.value);
The comparison allows us to search in given array.. for example :
Student : I have a string array with names "One
", "Two
", "Hook
", "Three
".. i want to find books which some of "title
" in this array..
Teacher : Well, give the names to Part.Value.. go..
//The "of" method search on current nodes' children //The "all" property selects all descendants with current TAXI.Selection selection = xml.drive .all .of ( Part.Name == "title" & Part.Value == new string[] { "One", "Two", "Hook", "Three" } ); MessageBox.Show(selection.value);
We used an "&
" operator in this example.. It is logical "AND
" for TAXI Query Parts..
The comparison allows to us find exactly unknown strings.. for example
Student : I remember there must be a book and its title is something like "Preju
".. How can I find it?
Teacher : Use Part class operators.. You need to use "*
" operator..
Operator * --- finds any contains
Operator + --- finds any starts with
Operator - --- finds any ends with
Opeator / --- finds any not contains
//The "of" method search on current nodes' children //The "all" property selects all descendants with current TAXI.Selection selection = xml.drive .all .of ( Part.Name == "title" & Part.Value * "Preju" ); MessageBox.Show(selection.value);
The Part class allows us to use TAXI.Selection as query value.. So, we can compare any data between selections easily.. for example :
Student : I have an "a
" attribute and this attribute has my genre name of some Book.. My question how can i select the book which genre attribute equals to any a attribute value?
Teacher : Easier than tell.. here it is..
TAXI.Selection selection = xml.drive .all .of ( Part.AttrValue["genre"] == xml.drive.all.of(Part.AttrValue["a"] != "").attr() ); MessageBox.Show(selection.value);
In this example we see the "attr()" method.. this method returns as TAXI.Selection too. and it selects attributes.. usage :
TAXI.Selection.attr() -- all attributes
TAXI.Selection.attr(params string[] names) --- attributes with given name
TAXI.Selection.attr(Query) --- attributes match with
Query
Student : I want to select the "book
" nodes. And also, i want to select "node
" nodes which has an "a
" attribute
Teacher : You need to give an "OR
" operator to Query.. Here it is..
TAXI.Selection selection = xml.drive .all .of ( (Query)"books" | (Query)(Part.AttrName == "a") ); MessageBox.Show(string.Join(",", selection.names));
Student : How many times I can call properties or methods ?
Teacher : Unlimited. You can call the methods or properties how many times you need. for example :
TAXI.Selection selection = xml.drive .all .of ( (Query)"books" | (Query)(Part.AttrName == "a") ) .parent.any.any.all.all.child.last.first .of(Element.Name["a", "vb", "c"]) .of(Element.Attr["q", "ISBN"]) .parentAndMe .prevs .nexts.........;
We used Element class to select attributes and nodes.. This class is abstract too. We will use it when we need to create Query by names. To create Query which selects attributes we should use Element.Attr otherwise we should use Element.Name
Element.Attr --- All attributes
Element.Attr[name1, name2, ...] --- Attributes by given name
Element.Name --- All children nodes
Element.Name[name1, name2, ...] --Nodes by given name
Student : If my query result is empty in any part. It will raise any error?
Teacher : No. As the example above, if first "of
" selects any node, still you can call the following selections. All after properties returns defaults.. I mean if you call "x
" and "ml.drive.all.of("z").any.all.child
of("z")
" has not any node than following selections will not raise any error.
Other Selection Methods
TAXI has RegEx support to match values or names..
Student : I have a regular expression and i want to search node names by this RegEx. How?
Teacher : "nameMatches(regexp)
" is the thing what you looked for!
//Matches the selected node names TAXI.Selection selection = xml.drive .any .nameMatches(reg); //Matches the selected node values TAXI.Selection selection = xml.drive .any .valueMatches(reg); //Matches the selected attribute names TAXI.Selection selection = xml.drive .any .attr() .nameMatches(reg); //Matches the selected attribute values TAXI.Selection selection = xml.drive .any .attr() .valueMatches(reg);
Student : I see that the Part Queries are case sensetive for strings. But, I want to match strings case insensetive. What should i do?
Teacher : TAXI's string support is enough to you. Just call compare methods. They are :
[name,value][,Not][StartsWith,Contains,EndsWith](string q [,bool isCaseSensetive])
Student : Opps! What is that above?
Teacher : Has all methods any combinations of above. for example : nameStartsWith
or valueNotEndsWith
//Search the nodes which name starts with "bo" TAXI.Selection selection = xml.drive .any .nameStartsWith("bo"); //Search the nodes which value contains "preJUDIce" TAXI.Selection selection = xml.drive .any .valueStartsWith("preJUDIce", false);
IMPORTANT : If the selected elements' type is Node then all operations works for Nodes, else if type is Attribute than all operations works for Attributes. If selected elements has one more type than all operations works for all. I mean, on the example above, the method works for nodes but if we call attr() before valueStartsWith it will check the attribute values..
Student : I want to select the 3rd
node of selection. How ?
Teacher : You can use name or integer indexes always.. Just call it. Here it is..
//Search the nodes which value contains "preJUDIce" //And gets the 3rd of result. TAXI.Selection selection = xml.drive .any .valueStartsWith("preJUDIce", false)[3];
Student : If the result has not 3 node than what will happen ?
Teacher : Nothing. No error. Feel free.
Student : If I want to select 3rd, 7th and 11th nodes. What should I do?
Teacher : Do the same.. give as parameters.. Also you can use string as parameters (as names).
//Search the nodes which value contains "preJUDIce" //And gets the 3rd, 7th and 11th of result. TAXI.Selection selection = xml.drive .any .valueStartsWith("bo", false)[3, 7, 11];
Student : I want to select the all nodes by distinct names or values.. What should I do?
Teacher : "distinct()" method. That is you need.
//Select all nodes with distinct names MessageBox.Show ( string.Join ( ",", xml.drive.all.distinct(MethodDistinct.Names).names ) ); //Select all attributes with distinct values MessageBox.Show ( string.Join ( ",", xml.drive.all.attr().distinct().values ) );
Student : I want to select the range of children of founded.. What should I do?
Teacher : range? well, call "range()"
//Select between 3rd to 7th nodes of founded MessageBox.Show ( string.Join ( ",", xml.drive.all.range(3, 7).names ) );
Getting Info
We finished to search and we found what we looked for. Now, it is the time to getting informations..
TAXI has many options to get information from nodes or attributes
The "name
", "names
", "value
", and "values
" Properties
name : The "name" property gives us the name of first founded node or attributes
names : The "names" property gives us all the names (string[]) of founded node or attributes
value : The "value" property gives us the value (inner text or value) of first founded node or attribute
values : The "values" property gives us all the values (string[]) of founded node or attributes
Student : I have DateTime
value in some "date
" attributes but some "date
" attributes are empty. And I want to get all values as DateTime
what should I do?
Teacher : Just call "nvalue<T>" or "nvalues<T>". This functions returns only parsed values.
DateTime[] allDates = xml.drive .any .attr("date") .nvalues<DateTime>();
nvalue<T> --- return the value of first selected node or attribute in given type. if value not parsed by given type, it will be ignored.
nvalues<T> --- return all the values of selected nodes or attributes in given type. if value not parsed by given type, it will be ignored.
Student : I want to get summary
or minimum
or maximum
or average
of selected values what should I do?
Teacher : use "sum" or "min" or "max" or "avg" properties :) they give you answers as double.
//Get the average of all attributes MessageBox.Show ( xml.drive.all.attr().avg.ToString() );
Student : That is ok bu I have another problem. I have time values on som attributes. how can I get their max, min, sum, or avg?
Teacher : No problem. add a "Of<T>()" string after that functions then call.. I mean :
like sumOf<T>(), minOf<T>(), maxOf<T>() or avgOf<T>()
//Get the summary of selected dates MessageBox.Show ( xml.drive.all.attr().sumOf<DateTime>().TimeOfDay.ToString() );
Student : That is cool. Another question about document. I want to create a document with my current selection. What should I do?
Teacher : Try to use "newDocument" property or "toNewDocument()" functions..
TAXI.Selection.newDocument --- creates a new document with current selections
TAXI.Selection.toNewDocument([string rootName, XmlWriterSettings settings]) -- does the same as above with the defined parameters.
TAXI newTAXI = xml.drive .all .of ( Part.Name == "book" & Part.AttrValue["genre"] == "novel" ).newDocument;
Student : I want to learn child indexes selected node and / or attributes
Teacher : "position
", "positions
" and "count
" properties return integers
TAXI.Selection selection = xml.drive.all; int curPosFirst = selection.position; int[] allPositions = selection.positions; int selectedCount = selection.count;
Modification
TAXI gives as many options to modify values or names..
Set Up!
Student : I want to change all selected node and/or attribute names.. ?
Teacher : The "name" property does what you need.
xml.drive.any.of("book").name = "bookItem";
Student : I want to change all selected node and/or attribute values.. ?
Teacher : same as above.. set "value"..
xml.drive.all.of(Element.Attr["date"]).value = DateTime.Now.ToString();
Simple Math!
Student : I want to increment my counter attribute.. ?
Teacher : Yes, you can.. Also, you can multiply, divide, substract, add or decrement values..
mul : multiplies
div : divides
add : adds
sub : substracts
inc : increments
dec : decrements
xml.drive.any["item"].attr("counter").inc();
xml.drive.any["item"].attr("counter").mul(5);
Node Jobs!
Student : I want to add attributes to all selected nodes.. how...?
Teacher : "addAttribute" will be a good choosen to you..
//add one xml.drive.all["book"].addAttribute("itemID", "0"); //add more than one MessageBox.Show ( string.Join ( ",", xml. drive. all["book"]. addAttributes(). add("a", "b"). add("c", "2"). add("d", "3"). add("e", "4") .finish() .attr() .names ) );
Student : I want to add new node to all founded nodes.. how ?
Teacher : add node.. should be "addNode"..
xml.drive. all["book"] .addNode(name, contentOptional);
Student : I want to add xml so is it "addXml" ? :)
Teacher : nearly.. "addXML" is fine..
Student : I want to delete selected node and/or attributes.. ?
Teacher : call "delete". :)
xml.all.attr("date").delete();
Student : I want to apply the selection to current TAXI document.. so?
Teacher : To apply call "apply";
xml.all.of(Part.Name == "book" & Part.AttrValue["date"] > DateTime.Now - 10).apply();
Student : I want to sort the selected nodes and / or attributes..
Teacher : user one of these
sort([XmlSortOrder, Type]) --- sorts by values in given Type and orders by XmlSortOrder
sortByName --- sorts by name
sortByAttr --- sorts by given attributes value
xml.all.of("book").sortByAttr("date");
Student : I want to converts all attributes to nodes.. is that possible ?
Teacher : Yes, call "aton"
xml.all.of("book").aton();
Student : I think, we can conver the nodes back to attributes, cant we?
Teacher : Sure, call "ntoa"
xml.all.all.all.any.any.any.ntoa();
Student : Woow! Finally, I want to play with selected nodes and / or attributes.. How can I do this ?
Teacher : give a "NodeEnumerationDelegate" delegate to "call" method..
xml .drive .all["book"] .call ( delegate(XPathNavigator navigator, int currentIndex, int count) { MessageBox.Show(navigator.Value); //return true to continue, false to break return true; } );
That is all.. Enjoy!
History
- 1/19/2009 First Release