Data-Selector

 view release on metacpan or  search on metacpan

lib/Data/Selector.pm  view on Meta::CPAN


=item last 2 links

 $selector_string = "items.*.links.+-2..-1"

 $data_tree = {
     items => [
         {
             links => [ 'l2', 'l3', ],
         },
         {
             links => [ 'l4', 'l5', ],
         },
     ],
 }

=back

=head1 METHODS

=cut

=over

=item parse_string

Creates a selector tree from a selector string.  A map of named selectors can
also be provided which will be interpolated into the selector string before it
is parsed.

Required Args:  selector_string
Optional Args:  named_selectors

=cut

my $selector_string_pattern = qr/
    (
        [^\[\]\,]*+
        (?:
            \[
                (?:
                    [^\[\]]++
                    |
                    (?1)
                )*
            \]
        )?+
    )
    ,?+
/x;

sub parse_string {
    my ( $class, $args, ) = @_;

    die "selector_string required\n"
      unless defined $args->{selector_string}
      && length $args->{selector_string};

    if ( index( $args->{selector_string}, '$', ) != -1 ) {
        $args->{selector_string} =~
          s/(?:(?<=^)|(?<=,))(\$[a-z_]*)(?:(,)(?!$)|$)/
            defined $args->{named_selectors}->{$1}
              && length $args->{named_selectors}->{$1}
              ? $args->{named_selectors}->{$1} . ( $2 ? $2 : '' )
              : die "contains invalid named selector\n";
        /ego;
    }

    my $selector_tree = {};
    my @queue = ( [ $args->{selector_string}, $selector_tree, [], ], );

    die "must be a string that matches /[^.\[\],]/\n"
      if length $args->{selector_string}
      && $args->{selector_string} !~ /[^.\[\],]/o;
    die "must not contain ']['\n" if index( $queue[0]->[0], '][' ) != -1;
    die "must not contain '[]'\n" if index( $queue[0]->[0], '[]' ) != -1;
    die "must not contain '[,'\n" if index( $queue[0]->[0], '[,' ) != -1;
    die "must not contain ',]'\n" if index( $queue[0]->[0], ',]' ) != -1;
    die "must not contain '[.'\n" if index( $queue[0]->[0], '[.' ) != -1;
    die "must not contain '.]'\n" if index( $queue[0]->[0], '.]' ) != -1;
    die "must not begin with','\n" if substr( $queue[0]->[0], 0, 1 ) eq ',';
    die "must not end with','\n"   if substr( $queue[0]->[0], -1, ) eq ',';
    die "must not begin with'.'\n" if substr( $queue[0]->[0], 0, 1 ) eq '.';
    die "must not end with'.'\n"   if substr( $queue[0]->[0], -1, ) eq '.';
    die "must have balanced [] chars\n"
      unless $queue[0]->[0] =~ tr/[/[/ == $queue[0]->[0] =~ tr/]/]/;
    die "must not match /[^.,]\[/\n"
      if $args->{selector_string} =~ /[^.,]\[/o;
    die "must not match /\][^.,\]]/\n"
      if $args->{selector_string} =~ /\][^.,\]]/o;

    my $order;
    while (@queue) {
        my $token  = shift @queue;
        my @groups = $token->[0] =~ /$selector_string_pattern/go;
        pop @groups;

        my ( $shift_a_suffix, $prev_is_suffix, );
        for my $string (@groups) {
            my $sub_tree = $token->[1];

            my $is_suffix = substr( $string, 0, 1, ) eq '.';
            if ($is_suffix) {
                push( @{ $queue[-1]->[2] }, substr( $string, 1, ), );
                $string = '';
            }
            else {
                my $opening_bracket_pos = index( $string, '[' );

                my $dot_in_prefix_pos = index( $string, '.' );
                $dot_in_prefix_pos = -1
                  if $opening_bracket_pos > -1
                  && $dot_in_prefix_pos > $opening_bracket_pos;

                if ( $dot_in_prefix_pos > -1 ) {
                    my $is_range =
                      substr( $string, $dot_in_prefix_pos + 1, 1 ) eq '.';
                    if ($is_range) {
                        $dot_in_prefix_pos =
                          index( $string, '.', $dot_in_prefix_pos + 2, );
                        $dot_in_prefix_pos = -1



( run in 0.719 second using v1.01-cache-2.11-cpan-5735350b133 )