Parsing a URL query string into a map of parameters with XPath - xpath

What would be the most readable way to parse a URL query string into a { 'param': 'value' } map in XSLT/XPath 3.0?
Note: this is the inverse function of the one described in Building a URL query string from a map of parameters with XPath.
Update: I neglected to mention that the function should support multi-value parameters such as a=1&a=2, and ideally parse them as an xs:string* sequence.

declare namespace map = "http://www.w3.org/2005/xpath-functions/map";
let $querystring := "a=1&b=2&c=3"
return
( tokenize($querystring, "&")
! (let $param := tokenize(., "=")
return map:entry($param[1], $param[2]) )
) => map:merge()
In order to support multiple values, you could can apply the $options parameter specifying what to do with duplicates:
declare namespace map = "http://www.w3.org/2005/xpath-functions/map";
let $querystring := "a=1&b=2&a=3"
return
( tokenize($querystring, "&")
! (let $param := tokenize(., "=")
return map:entry($param[1], $param[2]) )
) => map:merge(map:entry('duplicates', 'combine'))

2 more answers by Christian Grün:
let $querystring := "a=1&b=2&a=3"
return map:merge(
for $query in tokenize($querystring, "&")
let $param := tokenize($query, "=")
return map:entry(head($param), tail($param)),
map { 'duplicates': 'combine' }
)
One more solution (if you don’t wanna use the for clause):
let $querystring := "a=1&b=2&a=3"
return map:merge(
tokenize($querystring, "&")
! array { tokenize(., "=") }
! map:entry(.(1), .(2)),
map { 'duplicates': 'combine' }
)

let's see - substring to get ? and strip any trailing #... fragment identifier
then tokenize on [&;] (actually [;&] to get name=value pairs, which are separated by & or (less commonly) ;
then substring-before and after, or tokenize again, to get before and after the = (name value)
then uridecode the name and the value separately
let $query := substring-after($uri, '?'),
$beforefrag := substring-before($query || '#', '#')
return
tokenize($beforefrag, '[;&]')
! [substring-before(., '='), substring-after(., '=') ]
! map:entry(local:uridecode(.(1), local:uridecode(.(2))
might give us a sequene of map entries, and we can use map:merge on that.
If we know our input is plausibly encoded, we could use
declare function local:uridecode($input as xs:string?) as xs:string?
{
parse-xml-fragment(replace($input, '=(..)', '&x$1;'))
};
but a better version would just replace the two hex characters. It's really unfortunate we don't have a version of replace() that takes a function argument to be called for each matching subexpression, ala perl's e flag.```
and of course you can put that into
(...) => map:merge()

Related

MySQL escape string

How can I filter input from URL param example
localhost:8080/v1/data/:id
And I want to use filter like mysql_real_escape_string param for id in Golang, I can't use ? cause this filter is dynamic, this param can be use or no, like this example
if status != "99" {
where = append(where, "vs.stats = '1'")
}
if cari != "" {
where = append(where, "(lm.title_member like '%"+cari+"%' OR " +
"lm.nama_member like '%"+cari+"%' )")
}
query := "select vs.*, lm.nama_member from volks_shift vs left join list_member lm on vs.id_m=lm.id_m where vs.id_s=?"
rows, err := s.DB.QueryContext(ctx, query, id_s)
and I want secure cari val, without use ?
There is no escape function in the database/sql package, see related issue #18478 (and it's also not nice to invoke a mysql-specific function when using a database abstraction layer).
But it is also not needed as you can still use ? in a dynamic query. Just build the query parameters dynamically together with the query, like so:
query := "SELECT vs.*, lm.nama_member" +
" FROM volks_shift vs LEFT JOIN list_member lm ON vs.id_m=lm.id_m" +
" WHERE vs.id_s=?"
params := []interface{}{id_s}
if status != "99" {
query += " AND vs.stats = '1'"
}
if cari != "" {
query += " AND (lm.title_member LIKE ? OR lm.nama_member LIKE ?)"
params = append(params, "%"+cari+"%", "%"+cari+"%")
}
rows, err := s.DB.QueryContext(ctx, query, params...)

How to substitute the name of a node for a variable in a xquery search

I run this query successfully:
declare variable $CLIENTS as xs:string := '/resources/lists/clients.xml';
let $options := <options><node-name>Client</node-name></options>
let $search := '56385'
let $items :=
if ($options/node-name = 'Client') then
doc($CLIENTS)/Root/Item[contains(#Client, $search)]
else
if ($options/node-name = 'Name') then
doc($CLIENTS)/Root/Item[contains(#Name, $search)]
else
doc($CLIENTS)/Root/Item[contains(#Pub, $search)]
return $items/#Client
However, I would like to replace the attribute name with a variable so that I can pass the name as a parameter. By example:
let $items :=
doc($CLIENTS)/Root/Item[contains(...$options/node-name..., $search)]
This way I can search for any node without modifying the code.
Thanks.
You can use doc($CLIENTS)/Root/Item[contains(#*[local-name() = $options/node-name], $search)].

xquery randomly selecting files without duplicating the selection

In Xquery 3.1 (in eXist 4.7) I have 40 XML files, and I need to select 4 of them at random. However I would like the four files to be different.
My files are all in the same collection ($data). I currently count the files, then use a randomising function (util:random($max as xs:integer)) to generate position() in sequence of files to select four of them:
let $filecount := count($data)
for $cnt in 1 to 4
let $pos := util:random($filecount)
return $data[position()=$pos]
But this often results in the same files being selected multiple times by chance.
Each file has a distinct #xml:id (in the root node of each file) which can allow me, if possible, to use that as some sort of predicate in recursion. But I'm unable to identify a method for somehow accruing the #xml:ids into a cumulative, recursive sequence.
Thanks for any help.
I think the standardized random-numer-generator function and its permute function (https://www.w3.org/TR/xpath-functions/#func-random-number-generator) should give you better "randomness" and diverse results e.g.
let $file-count := count($data)
return $data[position() = random-number-generator(current-dateTime())?permute(1 to $file-count)[position() le 4]]
I haven't tried that with your db/XQuery implementation and it might be there are also ways with the functions you currently use.
For eXist-db I guess one strategy is to call the random-number function until you have got a distinct sequence of the wanted number of values, the following returns (at least in some tests with eXide)) four distinct numbers between 1 and 40 on each call:
declare function local:random-sequence($max as xs:integer, $length as xs:integer) as xs:integer+ {
local:random-sequence((), $max, $length)
};
declare function local:random-sequence($seq as xs:integer*, $max as xs:integer, $length as xs:integer) as xs:integer+ {
if (count($seq) = $length and $seq = distinct-values($seq))
then $seq
else local:random-sequence((distinct-values($seq), util:random($max)), $max, $length)
};
let $file-count := 40
return local:random-sequence($file-count, 4)
Integrating that in the previous attempt would result in
let $file-count := count($data)
return $data[position() = local:random-sequence($file-count, 4)]
As for your comment, I didn't notice the exist util:random function can return 0 and excludes the max value so based on your comment and a further test I guess you rather want the function I posted above to be implemented as
declare function local:random-sequence($seq as xs:integer*, $max as xs:integer, $length as xs:integer) as xs:integer+ {
if (count($seq) = $length)
then $seq
else
let $new-number := util:random($max + 1)
return if ($seq = $new-number or $new-number = 0)
then local:random-sequence($seq, $max, $length)
else local:random-sequence(($seq, $new-number), $max, $length)
};
That way it hopefully now returns $length distinct values between 1 and the $max argument.
It was such a fun question and interesting answer that I could not help myself than to play with local:random-sequence. Here is what I came up with:
(: needs zero-check, would return 1 item otherwise :)
declare function local:random-sequence($max as xs:integer, $length as xs:integer) as xs:integer* {
if ($length = 0)
then ()
else local:random-sequence((), $max, $length)
};
declare function local:random-sequence($seq as xs:integer*, $max as xs:integer, $length as xs:integer) as xs:integer+ {
let $new-number := util:random($max) + 1
let $new-seq :=
if ($seq = $new-number)
then $seq
else ($seq, $new-number)
return
if (count($new-seq) >= $length)
then $new-seq
else local:random-sequence($new-seq, $max, $length)
};
I think it is a little easier to read and grasp. It also saves 1 function call ;)

return a wide string from Win32::API call [duplicate]

The answer provided in wide char and win32::api works for passing utf-16 to the Win API. But how do I convert utf16 strings returned by the Win API? (I am trying to use GetCommandLineW).
I have tried both Unicode::String and Encode::decode without success. I'm guessing that perhaps the data needs to be packed or unpacked first, but how?
After that, the next problem is how to deal with a pointer-to-pointer-to-utf16 like the one returned by CommandLineToArgvW.
Thanks for any help.
When you specify the return value is a string, Win32::API assumes it's a terminated by a byte with value 0, but bytes with that value are common in UTF-16le text.
As Win32::API suggests, you should use the N type (or Q on 64-bit builds) to get the pointer as a number, then read the pointed memory yourself. Win32::API's provides ReadMemory to read memory, but it requires knowing how much memory to read. That's not useful for NUL-terminated strings and wide NUL-terminated strings.
For wide NUL-terminated strings, Win32::API provides SafeReadWideCString. But SafeReadWideCString can return a string unrelated to the input on error, so I use my own decode_LPCWSTR instead.
use strict;
use warnings;
use feature qw( say state );
use open ':std', ':encoding('.do { require Win32; "cp".Win32::GetConsoleOutputCP() }.')';
use Config qw( %Config );
use Encode qw( decode encode );
use Win32::API qw( ReadMemory );
use constant PTR_SIZE => $Config{ptrsize};
use constant PTR_PACK_FORMAT =>
PTR_SIZE == 8 ? 'Q'
: PTR_SIZE == 4 ? 'L'
: die("Unrecognized ptrsize\n");
use constant PTR_WIN32API_TYPE =>
PTR_SIZE == 8 ? 'Q'
: PTR_SIZE == 4 ? 'N'
: die("Unrecognized ptrsize\n");
sub lstrlenW {
my ($ptr) = #_;
state $lstrlenW = Win32::API->new('kernel32', 'lstrlenW', PTR_WIN32API_TYPE, 'i')
or die($^E);
return $lstrlenW->Call($ptr);
}
sub decode_LPCWSTR {
my ($ptr) = #_;
return undef if !$ptr;
my $num_chars = lstrlenW($ptr)
or return '';
return decode('UTF-16le', ReadMemory($ptr, $num_chars * 2));
}
# Returns true on success. Returns false and sets $^E on error.
sub LocalFree {
my ($ptr) = #_;
state $LocalFree = Win32::API->new('kernel32', 'LocalFree', PTR_WIN32API_TYPE, PTR_WIN32API_TYPE)
or die($^E);
return $LocalFree->Call($ptr) == 0;
}
sub GetCommandLine {
state $GetCommandLine = Win32::API->new('kernel32', 'GetCommandLineW', '', PTR_WIN32API_TYPE)
or die($^E);
return decode_LPCWSTR($GetCommandLine->Call());
}
# Returns a reference to an array on success. Returns undef and sets $^E on error.
sub CommandLineToArgv {
my ($cmd_line) = #_;
state $CommandLineToArgv = Win32::API->new('shell32', 'CommandLineToArgvW', 'PP', PTR_WIN32API_TYPE)
or die($^E);
my $cmd_line_encoded = encode('UTF-16le', $cmd_line."\0");
my $num_args_buf = pack('i', 0); # Allocate space for an "int".
my $arg_ptrs_ptr = $CommandLineToArgv->Call($cmd_line_encoded, $num_args_buf)
or return undef;
my $num_args = unpack('i', $num_args_buf);
my #args =
map { decode_LPCWSTR($_) }
unpack PTR_PACK_FORMAT.'*',
ReadMemory($arg_ptrs_ptr, PTR_SIZE * $num_args);
LocalFree($arg_ptrs_ptr);
return \#args;
}
{
my $cmd_line = GetCommandLine();
say $cmd_line;
my $args = CommandLineToArgv($cmd_line)
or die("CommandLineToArgv: $^E\n");
for my $arg (#$args) {
say "<$arg>";
}
}

Swift. Want an if statement that will give an error if value is not an integer

I am new to this an am trying to learn as much as I can. I have a variable that has a numerical value, I want an if statement that will look at this value and give an err if this value is not an integer. Can someone help?
Thanks
You can check it like this and then work with number inside the if clause:
if let number = numericalValue as? Int {
// numericalValue is an Int
} else {
// numericalValue is not an Int
}
I use Int() coupled with an if statement to achieve this:
//var number = 17 - will print "17 is an integer"
//var number = "abc" - will print "Error"
if let numberTest = Int(number) {
print("\(number) is an integer")
} else {
print("Error")
}
I managed to solve it in the end. I found the remainder operator in the Swift manual. Thought if I used that, divided by 1, if there was a remainder then the original value couldn't be an integer. So my code was - else if ((Double(guessEnteredNumber.text!)!) % 1 ) > 0 { resultText.text = "You need to guess a whole number between 1 and 5"
In swift 2 you can now use the 'guard' keyword like this
guard let number = myNumber as Int else {
// myNumber is not an Int
return
}
// myNumber is an Int and you can use number as it is not null
You can replace the 'let' keyword by a 'var' if you need to modify 'number' afterward

Resources