使用 XPath 从参数映射构建 URL 查询字符串
Posted
技术标签:
【中文标题】使用 XPath 从参数映射构建 URL 查询字符串【英文标题】:Building a URL query string from a map of parameters with XPath 【发布时间】:2021-07-23 22:54:24 【问题描述】:在 XSLT/XPath 3.0 中从 'param': 'value'
映射构建 URL 查询字符串的最易读的方法是什么?
【问题讨论】:
请注意——“最快”可能意味着运行速度最快。或者在编辑器中输入的最短时间(最简洁)。或者这需要最少的思考。相反,询问,什么是一种可读的方式来做到这一点。 (你用 local:build-uri() 得到的答案在这方面一点也不差) 另见反函数:***.com/questions/68944773/… 【参考方案1】:以下功能将起作用:
declare function local:build-uri($base-uri as xs:string, $params as map(xs:string, xs:string)) as xs:string
if (map:size($params) ne 0) then
let $param-string := string-join(
map:keys($params)[. ne ""] ! (encode-for-uri(.) || "=" || encode-for-uri($params?(.))),
"&"
)
return $base-uri || "?" || $param-string
else
$base-uri
;
例如:
declare namespace map = "http://www.w3.org/2005/xpath-functions/map";
declare variable $params := map
"one": "1",
"two": "2",
"three": "3",
"four": "4"
;
local:build-uri("http://www.example.com", map),
local:build-uri("http://www.example.com", $params),
local:build-uri("", $params),
()
返回:
http://www.example.com
http://www.example.com?four=4&one=1&two=2&three=3
?four=4&one=1&two=2&three=3
编辑:为了支持多值参数(同时保持函数体与 XPath 兼容),这样的事情应该可以工作:
declare function local:build-uri(
$base-uri as xs:string,
$params as map(xs:string, xs:string*),
$use-array-for-multivalue-params as xs:boolean (: param[]=value for php, etc. :)
) as xs:string
if (map:size($params) ne 0) then
let $param-strings :=
for $param in map:keys($params)[. ne '']
return $params?($param) ! string-join((
encode-for-uri($param),
if ($use-array-for-multivalue-params and count($params?($param)) gt 1) then "[]" else "",
"=",
encode-for-uri(.)
), "")
return $base-uri || "?" || string-join($param-strings, "&")
else
$base-uri
;
【讨论】:
如果有多个值的参数怎么办? ;) 您确定这适用于多值参数吗?我得到了https://localhost:4443/?uri=https://dbpedia.org/resource/Copenhagen&mode=https://w3id.org/atomgraph/client#EditMode,https://w3id.org/atomgraph/client#ModalMode
(URL 解码以提高可读性),而不是第二个mode
值用逗号连接。【参考方案2】:
不短,也不一定容易理解。
但是
它处理空值(使用 csv 你会得到key=
另一个完全省略键)
它处理 xs:anyAtomicType (xs:dateTime, xs:decimal, xs:boolean, ...)
添加了第三种常用方法来序列化查询字符串参数,其中多个值用逗号分隔
xquery version "3.1";
declare namespace qs="http://line-o.de/ns/qs";
(:~
: Append nothing to names of parameters with multiple values
: ?single=v1&multi=v2&multi=v3
:)
declare function qs:serialize-query-string($parameters as map(xs:string, xs:anyAtomicType*)) as xs:string?
qs:serialize(
$parameters,
qs:serialize-parameter(?, ?, ()))
;
(:~
: Append [] to names of parameters with multiple values
: ?single=v1&multi[]=v2&multi[]=v3
:)
declare function qs:serialize-query-string-array($parameters as map(xs:string, xs:anyAtomicType*)) as xs:string?
qs:serialize(
$parameters,
qs:serialize-parameter(?, ?, '[]'))
;
(:~
: Commma separated values for parameters with multiple values
: ?single=v1&multi=v2,v3
:)
declare function qs:serialize-query-string-csv($parameters as map(xs:string, xs:anyAtomicType*)) as xs:string?
qs:serialize(
$parameters,
qs:serialize-parameter-csv#2)
;
declare function qs:serialize(
$parameters as map(xs:string, xs:anyAtomicType*),
$serializer as function(xs:string, xs:anyAtomicType*) as xs:string*
) as xs:string?
if (map:size($parameters) eq 0)
then ()
else
$parameters
=> map:for-each($serializer)
=> string-join('&')
=> qs:prepend-questionmark()
;
declare function qs:serialize-parameter (
$raw-parameter-name as xs:string,
$values as xs:anyAtomicType*,
$appendix as xs:string?
) as xs:string*
let $parameter-name := concat(
encode-for-uri($raw-parameter-name),
if (exists($values) and count($values)) then $appendix else ()
)
return
for-each($values,
qs:serialize-parameter-value($parameter-name, ?))
;
declare function qs:serialize-parameter-csv ($raw-parameter-name as xs:string, $values as xs:anyAtomicType*) as xs:string*
concat(
encode-for-uri($raw-parameter-name),
'=',
$values
=> for-each(function ($value) encode-for-uri(xs:string($value)) )
=> string-join(',')
)
;
declare function qs:serialize-parameter-value (
$parameter as xs:string, $value as xs:anyAtomicType
) as xs:string
``[`$parameter`=`encode-for-uri($value)`]``
;
declare function qs:prepend-questionmark ($query-string as xs:string)
concat('?', $query-string)
;
qs:serialize-query-string(map),
qs:serialize-query-string-array(map),
qs:serialize-query-string-csv(map),
qs:serialize-query-string(map "a": ("b0","b1"), "b": "$=@#'" ),
qs:serialize-query-string-array(map "a": (xs:date("1970-01-01"),"b1"), "b": "$=@#'" ),
qs:serialize-query-string-csv(map "a": ("b0",3.14), "b": "$=@#'" ),
qs:serialize-query-string(map "a": ("b0","b1"), "c": () ),
qs:serialize-query-string-array(map "a": ("b0","b1"), "c": () ),
qs:serialize-query-string-csv(map "a": ("b0","b1"), "c": () )
以下是将上述内容拆分为模块和测试的要点:
https://gist.github.com/line-o/e492401494a4e003bb01b7a2f884b027
编辑:减少代码重复
【讨论】:
【参考方案3】:let
$encode-parameters-for-uri:= function($parameters as map(*)) as xs:string?
let
(: serialize each map entry :)
$encoded-parameters:= map:for-each(
$parameters,
function ($key, $values)
(: serialize the sequence of values for this key :)
for $value in $values return
encode-for-uri($key) || '=' || encode-for-uri($value)
),
(: join the URI parameters with ampersands :)
$parameters-string:= string-join(
$encoded-parameters,
codepoints-to-string(38)
)
return
(: prepend '?' if parameters exist :)
if ($parameters-string) then
'?' || $parameters-string
else
()
return
$encode-parameters-for-uri(
map
'size': 'large',
'flavour': ('chocolate', 'strawberry')
)
结果:?flavour=chocolate&flavour=strawberry&size=large
更简洁的版本,不同之处在于它将空映射转换为零长度字符串而不是空字符串序列:
let
$encode-parameters-for-uri:= function($parameters as map(*)) as xs:string
if (map:size($parameters)) then
'?' || string-join(
map:for-each(
$parameters,
function ($key, $values)
for $value in $values return
encode-for-uri($key) || '=' || encode-for-uri($value)
),
codepoints-to-string(38)
)
else
''
return
$encode-parameters-for-uri(
map
'foo': ('bar', 'baz'), 'direction': 'north'
)
结果?direction=north&foo=bar&foo=baz
【讨论】:
以上是关于使用 XPath 从参数映射构建 URL 查询字符串的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Swift 中使用包含多个值的查询参数构建 URL?