Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Drive the TAXI!

0.00/5 (No votes)
18 Jan 2009 2  
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 "xml.drive.all.of("z").any.all.child" and "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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here