package XML::XPath::Helper::String; use 5.008; use strict; use warnings; use Exporter 'import'; our $VERSION = '1.04'; our @EXPORT_OK = qw(quoted_string one_of_quoted not_one_of_quoted); our %EXPORT_TAGS = (all => \@EXPORT_OK); use Carp; sub quoted_string { @_ == 1 or croak("Wrong number of arguments"); my $arg = shift; my $arg_scalar; if (ref($arg)) { croak("Argument must be a string or a reference to an array") unless ref($arg) eq 'ARRAY'; } else { $arg_scalar = 1; $arg = "" if !defined($arg); $arg = [$arg]; } my @result; foreach my $string (@{$arg}) { if (index($string, "'") >= 0) { my @array; foreach my $substr (grep{length($_)} split(/('+)/, $string)) { my $q = substr($substr, 0, 1) eq "'" ? '"' : "'"; push(@array, "${q}${substr}${q}"); } push(@result, 'concat(' . join(',', @array) . ')'); } else { push(@result, "'$string'"); } } return ($arg_scalar ? $result[0] : \@result); } sub one_of_quoted { @_ > 0 or croak("Too few arguments"); my ($array, $name) = @_; ref($array) eq 'ARRAY' or croak("Argument 1 must be an ARRAY ref"); if (defined($name)) { ref($name) and croak("Argument 2 must be a scalar"); return "$name=" . join(" or $name=", @{quoted_string($array)}); } else { my $values = quoted_string($array); return sub { return "$_[0]=" . join(" or $_[0]=", @{$values}); }; } } sub not_one_of_quoted { @_ > 0 or croak("Too few arguments"); my ($array, $name) = @_; ref($array) eq 'ARRAY' or croak("Argument 1 must be an ARRAY ref"); if (defined($name)) { ref($name) and croak("Argument 2 must be a scalar"); return "$name!=" . join(" and $name!=", @{quoted_string($array)}); } else { my $values = quoted_string($array); return sub { return "$_[0]!=" . join(" and $_[0]!=", @{$values}); }; } } 1; # End of XML::XPath::Helper::String __END__ =pod =head1 NAME XML::XPath::Helper::String - Helper functions for xpath expression. =head1 VERSION Version 1.04 =head1 SYNOPSIS use XML::LibXML; use XML::XPath::Helper::String qw(quoted_string one_of_quoted); my @nodes = $myXML->findnodes('subnode=' . quoted_string("value with '")); my @other_nodes = $myXML->findnodes('foo_node[' . one_of_quoted("bar_node", "x'''y", "z") ']'); =head1 DESCRIPTION This modules provides functions that helps building xpath expressions. The functions are exported on demand, you can use the C<:all> tag to export all functions. =head2 FUNCTIONS =over =item C<quoted_string(I<ARG>)> This function makes it easier to create xpath expressions seaching for values that contains single quotes. The problem with xpath is that it does not support an escape character, so you have to use a C<concat(...)> in such cases. This function creates a C<concat(...)> expression if needed. I<C<ARG>> must be a string or a reference to an array of strings. If it is a string, the the function returns a string. If it is an array reference, then the function returns an array reference. For each string in I<C<ARG>> the function does the following: if the string does not contain any single quote, then the result is the string enclosed in single quotes. So this print(quoted_string("hello"), "\n"); prints: 'hello' But this print(quoted_string("'this' that \"x\" '''"), "\n"); prints: concat("'",'this',"'",' that "x" ',"'''") =item C<one_of_quoted(I<VALUES>, I<NAME>)> =item C<one_of_quoted(I<VALUES>)> This function creates an xpath expressions checking if I<C<NAME>> contains one of the values in I<C<VALUES>>. It calls C<quoted_string> to handle single quotes correctly. Example: This print(one_of_quoted(["'a'", "b'''cd", "e"], "foo"), "\n"); prints foo=concat("'",'a',"'") or foo=concat('b',"'''",'cd') or foo='e' If I<C<NAME>> is not specified, then the function returns a closure that takes one argument and produces the expression when called. Example: This my $closure = one_of_quoted(["'a'", "b'''cd", "e"]); print($closure->("foo"), "\n", $closure->("bar"), "\n"); prints foo=concat("'",'a',"'") or foo=concat('b',"'''",'cd') or foo='e' bar=concat("'",'a',"'") or bar=concat('b',"'''",'cd') or bar='e' =item C<not_one_of_quoted(I<VALUES>, I<NAME>)> =item C<not_one_of_quoted(I<VALUES>)> Like C<one_of_quoted> but creates an xpath expressions checking if I<C<NAME>> contains B<none> of the values in I<C<VALUES>>. Example: This: print(not_one_of_quoted("foo", "'a'", "b'''cd", "e"), "\n"); prints: foo!=concat("'",'a',"'") and foo!=concat('b',"'''",'cd') and foo!='e' =back =head1 SEE ALSO L<XML::LibXML>, L<XML::XPath::Helper::Const> =head1 AUTHOR Abdul al Hazred, C<< <451 at gmx.eu> >> =head1 BUGS Please report any bugs or feature requests to C<bug-xml-xpath-helper-string at rt.cpan.org>, or through the web interface at L<https://rt.cpan.org/NoAuth/ReportBug.html?Queue=XML-XPath-Helper-String>. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. =head1 SUPPORT You can find documentation for this module with the perldoc command. perldoc XML::XPath::Helper::String You can also look for information at: =over 4 =item * RT: CPAN's request tracker (report bugs here) L<https://rt.cpan.org/NoAuth/Bugs.html?Dist=XML-XPath-Helper-String> =item * Search CPAN L<https://metacpan.org/release/XML-XPath-Helper-String> =item * GitHub Repository L<https://github.com/AAHAZRED/perl-XML-XPath-Helper-String.git> =back =head1 LICENSE AND COPYRIGHT This software is copyright (c) 2022 by Abdul al Hazred. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut