Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

API for using xpath like queries against an arbitrary model.

tag: v0.3.4
ReadMe.textile

dNodi path query framework API

This API is intended to provide an infrastructure for querying a data structure using XPath like queries. dNodi provides the parsing and processing required to understand your query, so all you have to do is define the connection between dNodi and your data structure through a single protocol. In other words, you can relate a query to anything you like. For example, an XML data graph, UI object graph or indeed anything you think can be queried in this way.

Is this XPath?

Yes and No. It is based on the XPath language as specific at WC3School however it is not an exact match because the W3CSchool implementation is dedicated to XML where as dNodi is more generalised.

How did dNodi come about?

dNodi started out as a simple XPath executor in another project which dealt with SOAP web service requests. You can see that code at https://github.com/drekka/dXml Not long after that, I wanted to have similar functionality in another project, but this time against a UI object graph. So I pulled the code into a standalone library and started working on it to make it agnostic of the data it was querying. About this time I realised that what I was coding was not really XPAth so I renamed it to dNodi.

Installation

dNodi is distributed as a universal static framework for iOS devices. Simply download the DMG file, and copy the dNodi.framework directory to your hard drive. Then you can add it just like any other framework.

Dependencies

dNodi has the following dependencies:

If you wish to work with the source code of dNodi, ou will need GHUnit and OCMock. See bottom of this file for links.

How do I use it?

Getting ready

The first thing you need to do is to add the DNNode protocol to classes which provide the data you want to run queries against. The reason for this is that queries needs to be able to query for names, parent nodes, sub nodes, etc. By fulfilling this protocol your classes effectively become queriable. The tricky thing is that how you respond to the protocols methods is up to you. Here’s a summary of the protocols methods:

@property(readonly) NSString *dnName;
@property(readonly) NSObject<DNNode> *dnParentNode;
@property(readonly) NSArray * dnSubNodes;
-(BOOL) dnHasAttribute:(NSString *) attribute withValue:(id) value;

As you can see it’s very simple. dNodi does the heavy lifting of figuring out what it needs in order to understand your query. dNodi doesn’t make any assumptions about the data that comes back from these methods, so it’s up to you how you implement them.

When adressing attributes, dNodi is able to understand numbers and booleans, so you can pass through either NSNumbers (for BOOLs and numbers) or NSStrings to the hasAttribute:withValue: method.

Executing a query

For this you need the DNExecutor class. Here’s some sample code:

#import <dNodi/DNNode.h>
#import <dNodi/DNExecutor.h>
#import <NSObject+dNodi.h>

...

// The root node of your datastructure.
NSObject<DNNode> *rootNode = ...;

DNExecutor *executor = [[DNExecutor alloc] initWithRootNode:rootNode];

NSError *error = nil;
NSArray *results = [executor executeQuery:@"/rootNode/subnode" errorVar:&error];
if (results == nil) {
   // There's an error!
}
NSObject<DNNode> *firstFoundSubNode = [results nodeAtIndex:0];

Query Magic

First off the query syntax used here is slightly different to that outlined for XPaths at WC3School. Why? Simply because W3CSChool’s XPath is fairly specific to XML processing.

This library is all about using XPath against an unknown data structure, which may not be XML. So I’ve stuck as close as practical to the core xpath functionality as defined at WC3School web site. Here’s the list of supported query expressions you can use in a query requests to dNodi:

Expression Description
node-name Locates all direct sub node of the current node with the name node-name.
//node-name From the current node, locates all occurrences of node-name, no matter where they occur in the nodes below the current node. i.e. if a sub node has a sub node with the name, it will be returned. This is a good way to get to low level nodes without having to know how to get there. Effective it’s a search.
/ Has two meanings depending on where it occurs in the query, if it’s the first character in the query, this instructs the processor to start from the root node of the data, regardless of which node is passed to the query processor. So if you start with a node 3 levels down, this will traverse up through the parent nodes until it finds one without a parent. Otherwise it serves to seperate node names in the query expression.
.. Refers to the parent node of the current node. This is a shortcut that lets you effectively traverse back up the tree structure of your nodes.
[nn] Where nn is an integer. Can occur in a number of places. If preceeded by an node name, it returns the *n*th occurance of the node with that name. If not, it returns the *n*th occurance of any node. Note also that dNodi queries, unlike XPath, are 0 based, not 1 based when indexing nodes. So abc[0] refers to the first “abc” node.
[@attribue=‘value’] Selects nodes which have a matching atttribute and value. You can use either single () or double (") quotes to specify the value. Classes which implement DNNode will be passed the hasAttribute:withValue: message with the matching attribute name and value. If they return YES then the node is accepted. The value will be one of three possible values. A NSNumber containing a BOOL if the strings “yes”, “no”, “true” or "false are passed as the value. (Case insensitive). A NSDecimalNumber if the value contains nothing but an identifiable number. And finally a NSString if not one of the previous two types.

Query processing

Processing a query expression is fairly straight forward. The parser breaks up the expression into a series of tokens, and from those, determines a list of selectors and filters to execute. Selectors search the sub nodes of a particular node. Filters check the results of a selectors choices for further criteria. The end result is that a single node goes into the front of the list of selectors, and zero or more come out the other end.

Example queries

For arguments sake, lets assume that we have implemented DNNode on the classes that represent nodes in an XML data structure. As I said, it doesn’t have to be XML, but it’s the simplest thing to use for demo purposes. Here are some example queries using this example xml document:

<abc>                
   <def[@xyz='xxx']>  
      <ghi>lmn</ghi>  
      <ghi>rst</ghi>  
   </def>           
   <def>              
      <ghi>xyz</ghi>  
   </def>
   <opq /> 
</abc>
Starting node Query Returned nodes
<abc> /def <def[@xyz='xxx']>, <def> Note that it doesn’t matter from what DNNode you execute this from, it will still start from the root Node.
<abc> def <def[@xyz='xxx']>, <def> From the root node this is effective the same as /def, but from any other node it selects from that nodes sub nodes.
<def> ghi <ghi>xyz</ghi>
<abc> /def[0]/ghi <ghi>lmn</ghi>, <ghi>rst</ghi>
<def[@xyz='xxx']> ../def[1] <def> Parent (abc) and then the second “def” Node.
<abc> //ghi[2] <ghi>xyz</ghi> Locate all occurrences of “ghi” Nodes and return the third one.
<abc> //def/[0] <ghi>lmn</ghi>, <ghi>xyz</ghi> Locate all occurrences of “def” Nodes and returns the first sub node from each one.
<abc> /def[;@xyz='xxx']/ghi[1] <ghi>rst</ghi> Locates the second “ghi” node based on the attribute of the def node.

Future directions

This is very much a 1st release project built to satisfy a need in another project I have. Some of the future features I can see for dNodi include:

  • Various functions such as count() and text().
  • Callbacks.

Thanks

I’d like to say thanks to several developers for developing the following tools which have helped me tremendously is developing dNodi:

Something went wrong with that request. Please try again.