PrePAN

Sign in to PrePAN

Data::pQuery a xpath like processor for perl data-structures (hashes and arrays)

Author
jvverde@github
Date
URL
https://github.com/jvverde/pQuery
Status
In Review
Good

Synopsis

#!/usr/bin/perl
use strict;
use Data::pQuery;
use Data::Dumper;

($\,$,) = ("\n",",");
my $d = {
	drinks => {
		q|Alcoholic beverage| => 'not allowed',
		q|Soft drinks| => [qw|Soda Coke|]
	},
	food => { 
		fruit => [qw|bananas apples oranges pears|], 
		vegetables  => [qw|potatoes  carrots tomatoes|]
	} 
};

my $data = Data::pQuery->data($d);
my $results = $data->query(q|/*/*/0|);
my @values = $results->getvalues();
print @values;					
#Soda,bananas,potatoes

my $ref = $results->getref();
$$ref = 'Tonic';
print $d->{drinks}->{q|Soft drinks|}->[0];	
#Tonic

#keys with spaces or especial characters should be delimited 
#by double quotes 
print $data->query(q|/drinks/"Alcoholic beverage"|)->getvalues();
#not allowed

#or by single quotes
print $data->query(q|/drinks/'Soft drinks'[1]|)->getvalues();
#Coke

#the .. sequence indexes all array positions
print $data->query(q|/*/*[..]|)->getvalues();
#Tonic,Coke,bananas,apples,oranges,pears,potatoes,carrots,tomatoes

#the leading slash is optional
print $data->query(q|*/*[..]|)->getvalues(); 
#Tonic,Coke,bananas,apples,oranges,pears,potatoes,carrots,tomatoes

#negative values indexes the arrays in reverse order. -1 is the last index
print $data->query(q|/*/*[-1]|)->getvalues();
#Coke,pears,tomatoes

#Square brackets are also used to specify filters
print $data->query(q|/*/*[isScalar()]|)->getvalues();
#not allowed

#Like xpath a variable path length is defined by the sequence //
print $data->query(q|//*[isScalar()]|)->getvalues();
#not allowed

#The step ** select any key or any index, while the step * only select any key
print $data->query(q|//**[isScalar()]|)->getvalues();
#not allowed,Tonic,Coke,bananas,apples,oranges,pears,potatoes,carrots,tomatoes

#the filter could be a match between a string expression and a pattern
print $data->query(q|/*/*[name() ~ "drinks"][..]|)->getvalues();
#Tonic,Coke

#the same as above (in this particular data-strucure)
print $data->query(q|/*/*[name() ~ "drinks"]/**|)->getvalues();
#Tonic,Coke


#The returned values does not need to be scalars
print Dumper $data->query(q|/*/vegetables|)->getvalues();
=pod
$VAR1 = [
          'potatoes',
          'carrots',
          'tomatoes'
        ];
=cut

#using two filters in sequence 
print $data->query(q|
	//*
	[value([-1]) gt value([0])]
	[count([..]) < 4]
	[-1..0]
|)->getvalues();
#tomatoes,carrots,potatoes

#the same as above but using a logical operation instead of two filters
print $data->query(q|
	//*[value([-1]) gt value([0]) 
		and count([..]) < 4
	][-1..0]
|)->getvalues();
#tomatoes,carrots,potatoes

#a query could be a function instead of a path
print $data->query(q|names(/*/*)|)->getvalues();
#Alcoholic beverage,Soft drinks,fruit,vegetables

#the function 'names' returns the keys names or indexes
print $data->query(q|names(//**)|)->getvalues();
#drinks,Alcoholic beverage,Soft drinks,0,1,food,fruit,0,1,2,3,vegetables,0,1,2

print $data->query(q|names(//**/..)|)->getvalues();
#,,drinks,drinks,Soft drinks,Soft drinks,food,food,fruit,fruit,fruit,fruit,vegetables,vegetables,vegetables

Description

Why we need another one

There are already some good approaches to xpath syntax, namely the Data::dPath and Data::Path. Nevertheless we still missing some of the xpath powerfull constructions.

Suppose, for example, we have an array of invoices with Total, Amount and Tax and need to check which one does not comply to the rule "Total = Amount * (1+Tax)".

For the data structure below we can easily achieve it with this code:

use Data::pQuery;
use Data::Dumper;

($\,$,) = (qq|\n|, q|,|);
my $data = Data::pQuery->data([
        {invoice => {
                        Amount => 100,
                        Tax => 0.2,
                        Total => 120
                }
        },
        {invoice => {
                        Amount => 200,
                        Tax => 0.15,
                        Total => 240
                }       
        },
        receipt =>{ 
        }
]);

print Dumper $data->query(q$
        //invoice[Total != Amount * (1 + Tax)]
$)->getvalues();

The pQuery syntax is very similar to the xpath but with some minor exceptions. I am work on in to be close to xpath as much as possible.

Comments

Oh please, not another one. :( What new feature does this bring to the table, apart from yet another incompatible query language? Don't answer here, put the comparison with all the existing software into the documentation instead.

Data::Diver
Data::DPath/App::DPath
Data::Hierarchy
Data::Nested
Data::Path
Data::SPath
Hash::Path
JSON::Path
JSON::SL/JSON Pointer
Path::Resolver::Resolver::Hash

Also, the name is confusingly similar to pQuery (INGY/pQuery-0.09.tar.gz), a DOM manipulation library.
I will than you for your listing
I'm not familiar with the modules @daxim listed, but I do like the syntax I see here. This looks quite easy to use and, being very similar to XPath, there would be less of a learning curve for folks already familiar with that syntax.
Me neither for most of them except two!
My first attempt was to use Data::Path and Data::DPath but I found the syntax a little bit difficult and so far I realize it does not support typical xpath functions (count, name, sum and so one).
So after some googling I found Marpa::R2 and it seems to me to be a good project to learn it and try too emulate much possible the xpath.
However there are some constructions in both which does not match very well so I decide to change a little bit the language.
But now I have a comparison job to do.
Thank oalders for you encouragement.
It is now xpath compatible

Please sign up to post a review.