diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/css/tools/apiclient | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/css/tools/apiclient')
10 files changed, 1283 insertions, 0 deletions
diff --git a/testing/web-platform/tests/css/tools/apiclient/.gitignore b/testing/web-platform/tests/css/tools/apiclient/.gitignore new file mode 100644 index 0000000000..d43a721ddd --- /dev/null +++ b/testing/web-platform/tests/css/tools/apiclient/.gitignore @@ -0,0 +1,8 @@ +*.xcodeproj +*.DS_Store +local/* +*.log +*.pyc +*.orig +uritemplate-test/* +.hg/* diff --git a/testing/web-platform/tests/css/tools/apiclient/README.md b/testing/web-platform/tests/css/tools/apiclient/README.md new file mode 100644 index 0000000000..6e332b852a --- /dev/null +++ b/testing/web-platform/tests/css/tools/apiclient/README.md @@ -0,0 +1,174 @@ +apiclient +========== + +Client class for calling http+json APIs in Python. Requires Python 2.7. + +Supports json home pages per: +http://tools.ietf.org/html/draft-nottingham-json-home-03 + + +Installation +------------ +Standard python package installation: + + python setup.py install + + +Usage +----- +Import the apiclient package and instantiate a client. + + import apiclient + + api = apiclient.APIClient('http://api.example.com') + +Call APIs: + + result = api.call('foo', var1=arg1, var2=arg2) + print result.data + + +APIClient class +--------------- +**class apiclient.APIClient(baseURI, version = None, username = None, password = None)** + +The APIClient constructor takes the base URI for the api, an optional request version identifier, username and password. + +**APIClient.baseURI** + +The base URI set in the constructor, read-only. + +**APIClient.resourceNames** + +A list of available API resource names. + +**APIClient.resource(name)** + +Get a named APIResource. + +**APIClient.setVersion(name, version)** + +Set the request version identifier for a specific resource. If not set, the default version identifier will be used. + +**APIClient.setAccept(name, mimeType)** + +Set the requested Content-Type for a specific resource. If not set, 'application/json' will be used. + +**APIClient.get(name, [kwargs])** + +Perform an HTTP GET on the named resource. Any named arguments supplied may be used in computing the actual URI to call. Returns an APIResponse or None if the resource name is not known. + +**APIClient.postForm(name, payload = None, [kwargs])** + +Perform an HTTP POST on the named resource. The payload, if present, may be either a dict or sequence of two-tuples and will be form encoded. Any named arguments supplied may be used in computing the actual URI to call. Returns an APIResponse or None if the resource name is not known. + +**APIClient.put(name, payload = None, payloadType = None, [kwargs])** + +Perform an HTTP PUT on the named resource. The payload, if present, will be sent to the server using the payloadType Content-Type. The payload must be pre-encoded and will not be processed by the APIClient. Any named arguments supplied may be used in computing the actual URI to call. Returns an APIResponse or None if the resource name is not known. + +**APIClient.patch(name, patch = None, [kwargs])** + +Perform an HTTP PATCH on the named resource. The patch, if present, will be encoded in JSON and sent to the server as a 'application/json-patch'. Any named arguments supplied may be used in computing the actual URI to call. Returns an APIResponse or None if the resource name is not known. + +**APIClient.delete(name, [kwargs])** + +Perform an HTTP DELETE on the named resource. Any named arguments supplied may be used in computing the actual URI to call. Returns an APIResponse or None if the resource name is not known. + + +APIResponse class +----------------- +**APIResponse.status** + +The HTTP status code of the response. + +**APIResponse.headers** + +A dict of HTTP response headers. + +**APIResponse.contentType** + +The Content-Type of the response. + +**APIResponse.encoding** + +The encoding of the response. + +**APIResponse.data** + +The body of the response. If the contentType is json, the data will be decoded into native objects. + + +APIResource class +----------------- +Describes the properties of an available API resource. + +**APIResource.template** + +The URITemplate used when calling the resource. + +**APIResource.variables** + +A dict of variables that may be passed to the resource. Keys are variable names, values are the URI identifier of the variable, if available (see http://tools.ietf.org/html/draft-nottingham-json-home-03#section-3.1 ). + +**APIResource.hints** + +An APIHints object describing any hints for the resource (see http://tools.ietf.org/html/draft-nottingham-json-home-03#section-4 ). + + +APIHints class +-------------- +**APIHints.httpMethods** + +A list of HTTP methods the resource may be called with. + +**APIHints.formats** + +A dict of formats available for each HTTP method. Keys are HTTP methods, values are a list of Content-Types available. + +**APIHints.ranges** + +Not yet implemented. + +**APIHints.preferences** + +Not yet implemented. + +**APIHints.preconditions** + +Not yet implemented. + +**APIHints.auth** + +Not yet implemented. + +**APIHints.docs** + +A URI for documentation for the resource. + +**APIHints.status** + +The status of the resource. + + +URITemplate class +----------------- +Parses and expands URITemplates per RFC 6750 (plus a few extensions). + +**class uritemplate.URITemplate(template)** + +Construct a URITemplate. Raises exceptions if malformed. + +**URITemplate.variables** + +A set of variables available in the template. + +**URITemplate.expand([kwargs])** + +Return the expanded template substituting any passed keyword arguments. + + +Notes +----- +Resource names may be absolute URIs or relative to the base URI of the API. + + diff --git a/testing/web-platform/tests/css/tools/apiclient/__init__.py b/testing/web-platform/tests/css/tools/apiclient/__init__.py new file mode 100644 index 0000000000..b49652b48d --- /dev/null +++ b/testing/web-platform/tests/css/tools/apiclient/__init__.py @@ -0,0 +1,15 @@ +# coding=utf-8 +# +# Copyright © 2013 Hewlett-Packard Development Company, L.P. +# +# This work is distributed under the W3C® Software License [1] +# in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# [1] http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231 +# + +# define package for direct inclusion when not installed + +__all__ = ['apiclient']
\ No newline at end of file diff --git a/testing/web-platform/tests/css/tools/apiclient/apiclient/__init__.py b/testing/web-platform/tests/css/tools/apiclient/apiclient/__init__.py new file mode 100644 index 0000000000..9be8333de2 --- /dev/null +++ b/testing/web-platform/tests/css/tools/apiclient/apiclient/__init__.py @@ -0,0 +1,15 @@ +# coding=utf-8
+#
+# Copyright © 2013 Hewlett-Packard Development Company, L.P.
+#
+# This work is distributed under the W3C® Software License [1]
+# in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+#
+# [1] http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231
+#
+
+__all__ = ['apiclient', 'uritemplate']
+
+import apiclient
diff --git a/testing/web-platform/tests/css/tools/apiclient/apiclient/apiclient.py b/testing/web-platform/tests/css/tools/apiclient/apiclient/apiclient.py new file mode 100644 index 0000000000..99f30e6271 --- /dev/null +++ b/testing/web-platform/tests/css/tools/apiclient/apiclient/apiclient.py @@ -0,0 +1,283 @@ +# coding=utf-8
+#
+# Copyright © 2013 Hewlett-Packard Development Company, L.P.
+#
+# This work is distributed under the W3C® Software License [1]
+# in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+#
+# [1] http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231
+#
+
+# Process URI templates per http://tools.ietf.org/html/rfc6570
+
+
+import urllib2
+import urlparse
+import json
+import base64
+import contextlib
+import collections
+import UserString
+
+import uritemplate
+
+class MimeType(UserString.MutableString):
+ def __init__(self, mimeType):
+ UserString.MutableString.__init__(self, mimeType)
+ self._type = None
+ self._subtype = None
+ self._structure = None
+
+ slashIndex = mimeType.find('/')
+ if (-1 < slashIndex):
+ self._type = mimeType[:slashIndex]
+ mimeType = mimeType[slashIndex + 1:]
+ plusIndex = mimeType.find('+')
+ if (-1 < plusIndex):
+ self._subtype = mimeType[:plusIndex]
+ self._structure = mimeType[plusIndex + 1:]
+ else:
+ self._structure = mimeType
+ else:
+ self._type = mimeType
+
+ def _update(self):
+ if (self._structure):
+ if (self._subtype):
+ self.data = self._type + '/' + self._subtype + '+' + self._structure
+ else:
+ self.data = self._type + '/' + self._structure
+ else:
+ self.data = self._type
+
+ def set(self, type, structure, subtype = None):
+ self._type = type
+ self._subtype = subtype
+ self._structure = structure
+ self._update()
+
+ @property
+ def type(self):
+ return self._type
+
+ @type.setter
+ def type(self, value):
+ self._type = value
+ self._update()
+
+ @property
+ def subtype(self):
+ return self._subtype
+
+ @subtype.setter
+ def subtype(self, value):
+ self._subtype = value
+ self._update()
+
+ @property
+ def structure(self):
+ return self._structure
+
+ @structure.setter
+ def structure(self, value):
+ self._structure = value
+ self._update()
+
+
+class APIResponse(object):
+ def __init__(self, response):
+ self.status = response.getcode() if (response) else 0
+ self.headers = response.info() if (response) else {}
+ self.data = response.read() if (200 == self.status) else None
+
+ if (self.data and
+ (('json' == self.contentType.structure) or ('json-home' == self.contentType.structure))):
+ try:
+ self.data = json.loads(self.data, object_pairs_hook = collections.OrderedDict)
+ except:
+ pass
+
+ @property
+ def contentType(self):
+ contentType = self.headers.get('content-type') if (self.headers) else None
+ return MimeType(contentType.split(';')[0]) if (contentType and (';' in contentType)) else MimeType(contentType)
+
+ @property
+ def encoding(self):
+ contentType = self.headers.get('content-type') if (self.headers) else None
+ if (contentType and (';' in contentType)):
+ encoding = contentType.split(';', 1)[1]
+ if ('=' in encoding):
+ return encoding.split('=', 1)[1].strip()
+ return 'utf-8'
+
+
+class APIHints(object):
+ def __init__(self, data):
+ self.httpMethods = [method.upper() for method in data['allow'] if method] if ('allow' in data) else ['GET']
+ self.formats = {}
+ formats = [MimeType(format) for format in data['formats']] if ('formats' in data) else []
+ if (formats):
+ if ('GET' in self.httpMethods):
+ self.formats['GET'] = formats
+ if ('PUT' in self.httpMethods):
+ self.formats['PUT'] = formats
+
+ if (('PATCH' in self.httpMethods) and ('accept-patch' in data)):
+ self.formats['PATCH'] = [MimeType(format) for format in data['accept-patch']]
+ if (('POST' in self.httpMethods) and ('accept-post' in data)):
+ self.formats['POST'] = [MimeType(format) for format in data['accept-post']]
+
+ # TODO: ranges from 'accept-ranges'; preferece tokens from 'accept-prefer';
+ # preconditions from 'precondition-req'; auth from 'auth-req'
+ self.ranges = None
+ self.preferences = None
+ self.preconditions = None
+ self.auth = None
+
+ self.docs = data.get('docs')
+ self.status = data.get('status')
+
+
+class APIResource(object):
+ def __init__(self, baseURI, uri, variables = None, hints = None):
+ try:
+ self.template = uritemplate.URITemplate(urlparse.urljoin(baseURI, uri))
+ if (variables):
+ self.variables = {variable: urlparse.urljoin(baseURI, variables[variable]) for variable in variables}
+ else:
+ self.variables = {variable: '' for variable in self.template.variables}
+ self.hints = hints
+ except Exception as e:
+ self.template = uritemplate.URITemplate('')
+ self.variables = {}
+ self.hints = None
+
+
+class APIClient(object):
+ def __init__(self, baseURI, version = None, username = None, password = None):
+ self._baseURI = baseURI
+ self.defaultVersion = version
+ self.defaultAccept = 'application/json'
+ self.username = username
+ self.password = password
+ self._resources = {}
+ self._versions = {}
+ self._accepts = {}
+
+ self._loadHome()
+
+
+ @property
+ def baseURI(self):
+ return self._baseURI
+
+ def _loadHome(self):
+ home = self._callURI('GET', self.baseURI, 'application/home+json, application/json-home, application/json')
+ if (home):
+ if ('application/json' == home.contentType):
+ for name in home.data:
+ apiKey = urlparse.urljoin(self.baseURI, name)
+ self._resources[apiKey] = APIResource(self.baseURI, home.data[name])
+ elif (('application/home+json' == home.contentType) or
+ ('application/json-home' == home.contentType)):
+ resources = home.data.get('resources')
+ if (resources):
+ for name in resources:
+ apiKey = urlparse.urljoin(self.baseURI, name)
+ data = resources[name]
+ uri = data['href'] if ('href' in data) else data.get('href-template')
+ variables = data.get('href-vars')
+ hints = APIHints(data['hints']) if ('hints' in data) else None
+ self._resources[apiKey] = APIResource(self.baseURI, uri, variables, hints)
+
+
+ def relativeURI(self, uri):
+ if (uri.startswith(self.baseURI)):
+ relative = uri[len(self.baseURI):]
+ if (relative.startswith('/') and not self.baseURI.endswith('/')):
+ relative = relative[1:]
+ return relative
+ return uri
+
+ @property
+ def resourceNames(self):
+ return [self.relativeURI(apiKey) for apiKey in self._resources]
+
+ def resource(self, name):
+ return self._resources.get(urlparse.urljoin(self.baseURI, name))
+
+ def addResource(self, name, uri):
+ resource = APIResource(self.baseURI, uri)
+ apiKey = urlparse.urljoin(self.baseURI, name)
+ self._resources[apiKey] = resource
+
+ def _accept(self, resource):
+ version = None
+ if (api and (api in self._versions)):
+ version = self._versions[api]
+ if (not version):
+ version = self.defaultVersion
+ return ('application/' + version + '+json, application/json') if (version) else 'application/json'
+
+ def _callURI(self, method, uri, accept, payload = None, payloadType = None):
+ try:
+ request = urllib2.Request(uri, data = payload, headers = { 'Accept' : accept })
+ if (self.username and self.password):
+ request.add_header('Authorization', b'Basic ' + base64.b64encode(self.username + b':' + self.password))
+ if (payload and payloadType):
+ request.add_header('Content-Type', payloadType)
+ request.get_method = lambda: method
+
+ with contextlib.closing(urllib2.urlopen(request)) as response:
+ return APIResponse(response)
+ except Exception as e:
+ pass
+ return None
+
+ def _call(self, method, name, arguments, payload = None, payloadType = None):
+ apiKey = urlparse.urljoin(self.baseURI, name)
+ resource = self._resources.get(apiKey)
+
+ if (resource):
+ uri = resource.template.expand(**arguments)
+ if (uri):
+ version = self._versions.get(apiKey) if (apiKey in self._versions) else self.defaultVersion
+ accept = MimeType(self._accepts(apiKey) if (apiKey in self._accepts) else self.defaultAccept)
+ if (version):
+ accept.subtype = version
+ return self._callURI(method, uri, accept, payload, payloadType)
+ return None
+
+ def setVersion(self, name, version):
+ apiKey = urlparse.urljoin(self.baseURI, name)
+ self._versions[apiKey] = version
+
+ def setAccept(self, name, mimeType):
+ apiKey = urlparse.urljoin(self.baseURI, name)
+ self._accepts[apiKey] = mimeType
+
+ def get(self, name, **kwargs):
+ return self._call('GET', name, kwargs)
+
+ def post(self, name, payload = None, payloadType = None, **kwargs):
+ return self._call('POST', name, kwargs, payload, payloadType)
+
+ def postForm(self, name, payload = None, **kwargs):
+ return self._call('POST', name, kwargs, urllib.urlencode(payload), 'application/x-www-form-urlencoded')
+
+ def postJSON(self, name, payload = None, **kwargs):
+ return self._call('POST', name, kwargs, json.dumps(payload), 'application/json')
+
+ def put(self, name, payload = None, payloadType = None, **kwargs):
+ return self._call('PUT', name, kwargs, payload, payloadType)
+
+ def patch(self, name, patch = None, **kwargs):
+ return self._call('PATCH', name, kwargs, json.dumps(patch), 'application/json-patch')
+
+ def delete(self, name, **kwargs):
+ return self._call('DELETE', name, kwargs)
+
+
diff --git a/testing/web-platform/tests/css/tools/apiclient/apiclient/uritemplate.py b/testing/web-platform/tests/css/tools/apiclient/apiclient/uritemplate.py new file mode 100644 index 0000000000..737cbe7818 --- /dev/null +++ b/testing/web-platform/tests/css/tools/apiclient/apiclient/uritemplate.py @@ -0,0 +1,379 @@ +# coding=utf-8 +# +# Copyright © 2013 Hewlett-Packard Development Company, L.P. +# +# This work is distributed under the W3C® Software License [1] +# in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# [1] http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231 +# + +# Process URI templates per http://tools.ietf.org/html/rfc6570 + +import re + + +class UnsupportedExpression(Exception): + def __init__(self, expression): + self.expression = expression + + def __unicode__(self): + return u'Unsopported expression: ' + self.expression + +class BadExpression(Exception): + def __init__(self, expression): + self.expression = expression + + def __unicode__(self): + return u'Bad expression: ' + self.expression + +class BadVariable(Exception): + def __init__(self, variable): + self.variable = variable + + def __unicode__(self): + return u'Bad variable: ' + self.variable + +class BadExpansion(Exception): + def __init__(self, variable): + self.variable = variable + + def __unicode__(self): + return u'Bad expansion: ' + self.variable + +class URITemplate(object): + alpha = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' + digit = '0123456789' + hexdigit = '0123456789ABCDEFabcdef' + genDelims = ':/?#[]@' + subDelims = "!$&'()*+,;=" + varstart = alpha + digit + '_' + varchar = varstart + '.' + unreserved = alpha + digit + '-._~' + reserved = genDelims + subDelims + + def __init__(self, template): + self.template = template + + self.parts = [] + parts = re.split(r'(\{[^\}]*\})', self.template) + for part in parts: + if (part): + if (('{' == part[0]) and ('}' == part[-1])): + expression = part[1:-1] + if (re.match('^([a-zA-Z0-9_]|%[0-9a-fA-F][0-9a-fA-F]).*$', expression)): + self.parts.append(SimpleExpansion(expression)) + elif ('+' == part[1]): + self.parts.append(ReservedExpansion(expression)) + elif ('#' == part[1]): + self.parts.append(FragmentExpansion(expression)) + elif ('.' == part[1]): + self.parts.append(LabelExpansion(expression)) + elif ('/' == part[1]): + self.parts.append(PathExpansion(expression)) + elif (';' == part[1]): + self.parts.append(PathStyleExpansion(expression)) + elif ('?' == part[1]): + self.parts.append(FormStyleQueryExpansion(expression)) + elif ('&' == part[1]): + self.parts.append(FormStyleQueryContinuation(expression)) + elif (part[1] in '=,!@|'): + raise UnsupportedExpression(part) + else: + raise BadExpression(part) + else: + if (('{' not in part) and ('}' not in part)): + self.parts.append(Literal(part)) + else: + raise BadExpression(part) + + @property + def variables(self): + vars = set() + for part in self.parts: + vars.update(part.variables) + return vars + + def expand(self, **kwargs): + try: + expanded = [part.expand(kwargs) for part in self.parts] + except (BadExpansion): + return None + return ''.join([expandedPart for expandedPart in expanded if (expandedPart is not None)]) + + def __str__(self): + return self.template.encode('ascii', 'replace') + + def __unicode__(self): + return unicode(self.template) + + +class Variable(object): + def __init__(self, name): + self.name = '' + self.maxLength = None + self.explode = False + self.array = False + + if (name[0:1] not in URITemplate.varstart): + raise BadVariable(name) + + if (':' in name): + name, maxLength = name.split(':', 1) + if ((0 < len(maxLength)) and (len(maxLength) < 4)): + for digit in maxLength: + if (digit not in URITemplate.digit): + raise BadVariable(name + ':' + maxLength) + self.maxLength = int(maxLength) + if (not self.maxLength): + raise BadVariable(name + ':' + maxLength) + else: + raise BadVariable(name + ':' + maxLength) + elif ('*' == name[-1]): + name = name[:-1] + self.explode = True + elif ('[]' == name[-2:]): + name = name[:-2] + self.array = True + self.explode = True + + index = 0 + while (index < len(name)): + codepoint = name[index] + if (('%' == codepoint) and + ((index + 2) < len(name)) and + (name[index + 1] in URITemplate.hexdigit) and + (name[index + 2] in URITemplate.hexdigit)): + self.name += name[index:index + 3] + index += 2 + elif (codepoint in URITemplate.varchar): + self.name += codepoint + else: + raise BadVariable(name + ((':' + self.maxLength) if (self.maxLength) else '') + ('[]' if (self.array) else ('*' if (self.explode) else ''))) + index += 1 + + +class Expression(object): + def __init__(self): + pass + + @property + def variables(self): + return [] + + def _encode(self, value, legal, pctEncoded): + output = '' + index = 0 + while (index < len(value)): + codepoint = value[index] + if (codepoint in legal): + output += codepoint + elif (pctEncoded and ('%' == codepoint) and + ((index + 2) < len(value)) and + (value[index + 1] in URITemplate.hexdigit) and + (value[index + 2] in URITemplate.hexdigit)): + output += value[index:index + 3] + index += 2 + else: + utf8 = codepoint.encode('utf8') + for byte in utf8: + output += '%' + URITemplate.hexdigit[ord(byte) / 16] + URITemplate.hexdigit[ord(byte) % 16] + index += 1 + return output + + def _uriEncodeValue(self, value): + return self._encode(value, URITemplate.unreserved, False) + + def _uriEncodeName(self, name): + return self._encode(unicode(name), URITemplate.unreserved + URITemplate.reserved, True) if (name) else '' + + def _join(self, prefix, joiner, value): + if (prefix): + return prefix + joiner + value + return value + + def _encodeStr(self, variable, name, value, prefix, joiner, first): + if (variable.maxLength): + if (not first): + raise BadExpansion(variable) + return self._join(prefix, joiner, self._uriEncodeValue(value[:variable.maxLength])) + return self._join(prefix, joiner, self._uriEncodeValue(value)) + + def _encodeDictItem(self, variable, name, key, item, delim, prefix, joiner, first): + joiner = '=' if (variable.explode) else ',' + if (variable.array): + prefix = (prefix + '[' + self._uriEncodeName(key) + ']') if (prefix and not first) else self._uriEncodeName(key) + else: + prefix = self._join(prefix, '.', self._uriEncodeName(key)) + return self._encodeVar(variable, key, item, delim, prefix, joiner, False) + + def _encodeListItem(self, variable, name, index, item, delim, prefix, joiner, first): + if (variable.array): + prefix = prefix + '[' + unicode(index) + ']' if (prefix) else '' + return self._encodeVar(variable, None, item, delim, prefix, joiner, False) + return self._encodeVar(variable, name, item, delim, prefix, '.', False) + + def _encodeVar(self, variable, name, value, delim = ',', prefix = '', joiner = '=', first = True): + if (isinstance(value, basestring)): + return self._encodeStr(variable, name, value, prefix, joiner, first) + elif (hasattr(value, 'keys') and hasattr(value, '__getitem__')): # dict-like + if (len(value)): + encodedItems = [self._encodeDictItem(variable, name, key, value[key], delim, prefix, joiner, first) for key in value.keys()] + return delim.join([item for item in encodedItems if (item is not None)]) + return None + elif (hasattr(value, '__getitem__')): # list-like + if (len(value)): + encodedItems = [self._encodeListItem(variable, name, index, item, delim, prefix, joiner, first) for index, item in enumerate(value)] + return delim.join([item for item in encodedItems if (item is not None)]) + return None + else: + return self._encodeStr(variable, name, unicode(value).lower(), prefix, joiner, first) + + def expand(self, values): + return None + + +class Literal(Expression): + def __init__(self, value): + Expression.__init__(self) + self.value = value + + def expand(self, values): + return self._encode(self.value, (URITemplate.unreserved + URITemplate.reserved), True) + + +class Expansion(Expression): + operator = '' + varJoiner = ',' + + def __init__(self, variables): + Expression.__init__(self) + self.vars = [Variable(var) for var in variables.split(',')] + + @property + def variables(self): + return [var.name for var in self.vars] + + def _expandVar(self, variable, value): + return self._encodeVar(variable, self._uriEncodeName(variable.name), value) + + def expand(self, values): + expandedVars = [] + for var in self.vars: + if ((var.name in values) and (values[var.name] is not None)): + expandedVar = self._expandVar(var, values[var.name]) + if (expandedVar is not None): + expandedVars.append(expandedVar) + if (len(expandedVars)): + expanded = self.varJoiner.join(expandedVars) + if (expanded is not None): + return self.operator + expanded + return None + + +class SimpleExpansion(Expansion): + def __init__(self, variables): + Expansion.__init__(self, variables) + + +class ReservedExpansion(Expansion): + def __init__(self, variables): + Expansion.__init__(self, variables[1:]) + + def _uriEncodeValue(self, value): + return self._encode(value, (URITemplate.unreserved + URITemplate.reserved), True) + + +class FragmentExpansion(ReservedExpansion): + operator = '#' + + def __init__(self, variables): + ReservedExpansion.__init__(self, variables) + + +class LabelExpansion(Expansion): + operator = '.' + varJoiner = '.' + + def __init__(self, variables): + Expansion.__init__(self, variables[1:]) + + def _expandVar(self, variable, value): + return self._encodeVar(variable, self._uriEncodeName(variable.name), value, delim = ('.' if variable.explode else ',')) + + +class PathExpansion(Expansion): + operator = '/' + varJoiner = '/' + + def __init__(self, variables): + Expansion.__init__(self, variables[1:]) + + def _expandVar(self, variable, value): + return self._encodeVar(variable, self._uriEncodeName(variable.name), value, delim = ('/' if variable.explode else ',')) + + +class PathStyleExpansion(Expansion): + operator = ';' + varJoiner = ';' + + def __init__(self, variables): + Expansion.__init__(self, variables[1:]) + + def _encodeStr(self, variable, name, value, prefix, joiner, first): + if (variable.array): + if (name): + prefix = prefix + '[' + name + ']' if (prefix) else name + elif (variable.explode): + prefix = self._join(prefix, '.', name) + return Expansion._encodeStr(self, variable, name, value, prefix, joiner, first) + + def _encodeDictItem(self, variable, name, key, item, delim, prefix, joiner, first): + if (variable.array): + if (name): + prefix = prefix + '[' + name + ']' if (prefix) else name + prefix = (prefix + '[' + self._uriEncodeName(key) + ']') if (prefix and not first) else self._uriEncodeName(key) + elif (variable.explode): + prefix = self._join(prefix, '.', name) if (not first) else '' + else: + prefix = self._join(prefix, '.', self._uriEncodeName(key)) + joiner = ',' + return self._encodeVar(variable, self._uriEncodeName(key) if (not variable.array) else '', item, delim, prefix, joiner, False) + + def _encodeListItem(self, variable, name, index, item, delim, prefix, joiner, first): + if (variable.array): + if (name): + prefix = prefix + '[' + name + ']' if (prefix) else name + return self._encodeVar(variable, unicode(index), item, delim, prefix, joiner, False) + return self._encodeVar(variable, name, item, delim, prefix, '=' if (variable.explode) else '.', False) + + def _expandVar(self, variable, value): + if (variable.explode): + return self._encodeVar(variable, self._uriEncodeName(variable.name), value, delim = ';') + value = self._encodeVar(variable, self._uriEncodeName(variable.name), value, delim = ',') + return (self._uriEncodeName(variable.name) + '=' + value) if (value) else variable.name + + +class FormStyleQueryExpansion(PathStyleExpansion): + operator = '?' + varJoiner = '&' + + def __init__(self, variables): + PathStyleExpansion.__init__(self, variables) + + def _expandVar(self, variable, value): + if (variable.explode): + return self._encodeVar(variable, self._uriEncodeName(variable.name), value, delim = '&') + value = self._encodeVar(variable, self._uriEncodeName(variable.name), value, delim = ',') + return (self._uriEncodeName(variable.name) + '=' + value) if (value is not None) else None + + +class FormStyleQueryContinuation(FormStyleQueryExpansion): + operator = '&' + + def __init__(self, variables): + FormStyleQueryExpansion.__init__(self, variables) + + diff --git a/testing/web-platform/tests/css/tools/apiclient/setup.py b/testing/web-platform/tests/css/tools/apiclient/setup.py new file mode 100644 index 0000000000..e3b0937638 --- /dev/null +++ b/testing/web-platform/tests/css/tools/apiclient/setup.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python
+
+from distutils.core import setup
+
+setup(name='apiclient',
+ version='0.10',
+ description='Client for JSPN APIs',
+ author='Peter Linss',
+ author_email='peter.linss@hp.com',
+ url='http://github.com/plinss/apiclient/',
+ packages = ['apiclient']
+ )
diff --git a/testing/web-platform/tests/css/tools/apiclient/test.py b/testing/web-platform/tests/css/tools/apiclient/test.py new file mode 100755 index 0000000000..e485367f84 --- /dev/null +++ b/testing/web-platform/tests/css/tools/apiclient/test.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python +# coding=utf-8 +# +# Copyright © 2013 Hewlett-Packard Development Company, L.P. +# +# This work is distributed under the W3C® Software License [1] +# in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# [1] http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231 +# + +from __future__ import print_function +import sys +import os +import glob +import json +import exceptions +import collections + +from apiclient import uritemplate +from apiclient import apiclient + + +def runTests(testFileSearch): + for testFilePath in glob.glob(testFileSearch): + print('Running tests from: ' + testFilePath) + with open(testFilePath) as testFile: + testData = json.load(testFile, object_pairs_hook = collections.OrderedDict) + for testSetName in testData: + print(testSetName + ':') + testSet = testData[testSetName] + vars = testSet['variables'] + for test in testSet['testcases']: + expectedResult = test[1] + try: + template = uritemplate.URITemplate(test[0]) + except Exception as e: + if (expectedResult): + print('* FAIL: "' + test[0] + '" got: None, expected "' + expectedResult + '"') + else: + print(' PASS: "' + test[0] + '" == None') + continue + + result = template.expand(**vars) + if (isinstance(expectedResult, basestring)): + if (expectedResult != result): + print('* FAIL: "' + test[0] + '" got: "' + unicode(result) + '", expected "' + expectedResult + '"') + continue + elif (isinstance(expectedResult, list)): + for possibleResult in expectedResult: + if (possibleResult == result): + break + else: + print('* FAIL: "' + test[0] + '" got: "' + unicode(result) + '", expected:') + print(" or\n".join([' "' + possibleResult + '"' for possibleResult in expectedResult])) + continue + elif (not expectedResult): + if (result): + print('* FAIL "' + test[0] + '" got: "' + unicode(result) + '", expected None') + continue + else: + print('** Unknown expected result type: ' + repr(expectedResult)) + print(' PASS: "' + test[0] + '" == "' + result + '"') + +def debugHook(type, value, tb): + if hasattr(sys, 'ps1') or not sys.stderr.isatty(): + # we are in interactive mode or we don't have a tty-like + # device, so we call the default hook + sys.__excepthook__(type, value, tb) + else: + import traceback, pdb + # we are NOT in interactive mode, print the exception... + traceback.print_exception(type, value, tb) + print() + # ...then start the debugger in post-mortem mode. + pdb.pm() + + +if __name__ == "__main__": # called from the command line + sys.excepthook = debugHook + +# runTests(os.path.join('test', '*.json')) + +# runTests(os.path.join('uritemplate-test', 'spec-examples.json')) +# runTests(os.path.join('uritemplate-test', '*.json')) +### more tests @ https://github.com/uri-templates/uritemplate-test + + + github = apiclient.APIClient('https://api.github.com/', version = 'vnd.github.beta') + print(github.get('user_url', user = 'plinss').data) + +# shepherd = apiclient.APIClient('https://api.csswg.org/shepherd/', version = 'vnd.csswg.shepherd.v1') + shepherd = apiclient.APIClient('https://test.linss.com/shepherd/api', version = 'vnd.csswg.shepherd.v1') + print(shepherd.resourceNames) + specs = shepherd.resource('specifications') + print(specs.variables) +# print specs.hints.docs + print(shepherd.get('specifications', spec = 'compositing-1', anchors = False).data) + + suites = shepherd.resource('test_suites') + print(suites.variables) + print(shepherd.get('test_suites', spec = 'css-shapes-1').data) + + diff --git a/testing/web-platform/tests/css/tools/apiclient/test/corners.json b/testing/web-platform/tests/css/tools/apiclient/test/corners.json new file mode 100644 index 0000000000..fd0a5a80f8 --- /dev/null +++ b/testing/web-platform/tests/css/tools/apiclient/test/corners.json @@ -0,0 +1,81 @@ +{
+ "Explode Non-composite" :
+ {
+ "level": 4,
+ "variables": {
+ "token": "12345"
+ },
+ "testcases" : [
+ ["{token}", "12345"],
+ ["{token*}", "12345"],
+ ["{.token}", ".12345"],
+ ["{.token*}", ".12345"],
+ ["{/token}", "/12345"],
+ ["{/token*}", "/12345"],
+ ["{?token}", "?token=12345"],
+ ["{?token*}", "?token=12345"]
+ ]
+ },
+ "Non-string Values" :
+ {
+ "level": 4,
+ "variables": {
+ "positive": true,
+ "negative": false,
+ "zero" : 0,
+ "number" : 42.24,
+ "list" : ["one", 2, true, false, 0],
+ "dict" : { "one": "1", "two": 2, "positive": true, "negative": false, "zero": 0 }
+ },
+ "testcases" : [
+ ["{positive}", "true"],
+ ["{positive*}", "true"],
+ ["{negative}", "false"],
+ ["{negative*}", "false"],
+ ["{zero}", "0"],
+ ["{zero*}", "0"],
+ ["{number}", "42.24"],
+ ["{number*}", "42.24"],
+ ["{.positive}", ".true"],
+ ["{.positive*}", ".true"],
+ ["{.negative}", ".false"],
+ ["{.negative*}", ".false"],
+ ["{.zero}", ".0"],
+ ["{.zero*}", ".0"],
+ ["{.number}", ".42.24"],
+ ["{.number*}", ".42.24"],
+ ["{/positive}", "/true"],
+ ["{/positive*}", "/true"],
+ ["{/negative}", "/false"],
+ ["{/negative*}", "/false"],
+ ["{/zero}", "/0"],
+ ["{/zero*}", "/0"],
+ ["{/number}", "/42.24"],
+ ["{/number*}", "/42.24"],
+ ["{?positive}", "?positive=true"],
+ ["{?positive*}", "?positive=true"],
+ ["{?negative}", "?negative=false"],
+ ["{?negative*}", "?negative=false"],
+ ["{?zero}", "?zero=0"],
+ ["{?zero*}", "?zero=0"],
+ ["{?number}", "?number=42.24"],
+ ["{?number*}", "?number=42.24"],
+ ["{list}", "one,2,true,false,0"],
+ ["{list*}", "one,2,true,false,0"],
+ ["{.list}", ".one,2,true,false,0"],
+ ["{.list*}", ".one.2.true.false.0"],
+ ["{/list}", "/one,2,true,false,0"],
+ ["{/list*}", "/one/2/true/false/0"],
+ ["{?list}", "?list=one,2,true,false,0"],
+ ["{?list*}", "?list=one&list=2&list=true&list=false&list=0"],
+ ["{dict}", "one,1,two,2,positive,true,negative,false,zero,0"],
+ ["{dict*}", "one=1,two=2,positive=true,negative=false,zero=0"],
+ ["{.dict}", ".one,1,two,2,positive,true,negative,false,zero,0"],
+ ["{.dict*}", ".one=1.two=2.positive=true.negative=false.zero=0"],
+ ["{/dict}", "/one,1,two,2,positive,true,negative,false,zero,0"],
+ ["{/dict*}", "/one=1/two=2/positive=true/negative=false/zero=0"],
+ ["{?dict}", "?dict=one,1,two,2,positive,true,negative,false,zero,0"],
+ ["{?dict*}", "?one=1&two=2&positive=true&negative=false&zero=0"]
+ ]
+ }
+}
diff --git a/testing/web-platform/tests/css/tools/apiclient/test/extensions.json b/testing/web-platform/tests/css/tools/apiclient/test/extensions.json new file mode 100644 index 0000000000..0c493620bd --- /dev/null +++ b/testing/web-platform/tests/css/tools/apiclient/test/extensions.json @@ -0,0 +1,210 @@ +{ + "Nested Structures" : + { + "level": 5, + "variables": { + "list" : ["one", "two", "three", "four"], + "dict" : {"semi": ";", "dot": ".", "comma": ","}, + "lists" : [["one", "two"], ["three"], "four"], + "dicts" : {"one": {"semi": ";", "dot": "."}, "two": {"comma": ","}}, + "mixed" : {"list": ["one", ["two", "three"]], "dict": {"one": {"semi": ";", "dot": "."}, "two": {"comma": ","}}}, + "dlist" : [{"semi": ";", "dot": "."}, {"comma": ","}] + }, + "testcases" : [ + ["{list}", "one,two,three,four"], + ["{list*}", "one,two,three,four"], + ["{?list}", "?list=one,two,three,four"], + ["{?list*}", "?list=one&list=two&list=three&list=four"], + ["{lists}", "one,two,three,four"], + ["{lists*}", "one,two,three,four"], + ["{?lists}", "?lists=one,two,three,four"], + ["{?lists*}", "?lists=one&lists=two&lists=three&lists=four"], + ["{dict}", [ + "comma,%2C,dot,.,semi,%3B", + "comma,%2C,semi,%3B,dot,.", + "dot,.,comma,%2C,semi,%3B", + "dot,.,semi,%3B,comma,%2C", + "semi,%3B,comma,%2C,dot,.", + "semi,%3B,dot,.,comma,%2C" + ]], + ["{dict*}", [ + "comma=%2C,dot=.,semi=%3B", + "comma=%2C,semi=%3B,dot=.", + "dot=.,comma=%2C,semi=%3B", + "dot=.,semi=%3B,comma=%2C", + "semi=%3B,comma=%2C,dot=.", + "semi=%3B,dot=.,comma=%2C" + ]], + ["{?dict}", [ + "?dict=comma,%2C,dot,.,semi,%3B", + "?dict=comma,%2C,semi,%3B,dot,.", + "?dict=dot,.,comma,%2C,semi,%3B", + "?dict=dot,.,semi,%3B,comma,%2C", + "?dict=semi,%3B,comma,%2C,dot,.", + "?dict=semi,%3B,dot,.,comma,%2C" + ]], + ["{?dict*}", [ + "?comma=%2C&dot=.&semi=%3B", + "?comma=%2C&semi=%3B&dot=.", + "?dot=.&comma=%2C&semi=%3B", + "?dot=.&semi=%3B&comma=%2C", + "?semi=%3B&comma=%2C&dot=.", + "?semi=%3B&dot=.&comma=%2C" + ]], + ["{dicts}", [ + "two.comma,%2C,one.dot,.,one.semi,%3B", + "two.comma,%2C,one.semi,%3B,one.dot,.", + "one.dot,.,two.comma,%2C,one.semi,%3B", + "one.dot,.,one.semi,%3B,two.comma,%2C", + "one.semi,%3B,two.comma,%2C,one.dot,.", + "one.semi,%3B,one.dot,.,two.comma,%2C" + ]], + ["{dicts*}", [ + "two.comma=%2C,one.dot=.,one.semi=%3B", + "two.comma=%2C,one.semi=%3B,one.dot=.", + "one.dot=.,two.comma=%2C,one.semi=%3B", + "one.dot=.,one.semi=%3B,two.comma=%2C", + "one.semi=%3B,two.comma=%2C,one.dot=.", + "one.semi=%3B,one.dot=.,two.comma=%2C" + ]], + ["{?dicts}", [ + "?dicts=two.comma,%2C,one.dot,.,one.semi,%3B", + "?dicts=two.comma,%2C,one.semi,%3B,one.dot,.", + "?dicts=one.dot,.,two.comma,%2C,one.semi,%3B", + "?dicts=one.dot,.,one.semi,%3B,two.comma,%2C", + "?dicts=one.semi,%3B,two.comma,%2C,one.dot,.", + "?dicts=one.semi,%3B,one.dot,.,two.comma,%2C" + ]], + ["{?dicts*}", [ + "?two.comma=%2C&one.dot=.&one.semi=%3B", + "?two.comma=%2C&one.semi=%3B&one.dot=.", + "?one.dot=.&two.comma=%2C&one.semi=%3B", + "?one.dot=.&one.semi=%3B&two.comma=%2C", + "?one.semi=%3B&two.comma=%2C&one.dot=.", + "?one.semi=%3B&one.dot=.&two.comma=%2C" + ]], + ["{mixed}", [ + "list.one,list.two,list.three,dict.two.comma,%2C,dict.one.dot,.,dict.one.semi,%3B", + "list.one,list.two,list.three,dict.two.comma,%2C,dict.one.semi,%3B,dict.one.dot,.", + "list.one,list.two,list.three,dict.one.dot,.,dict.two.comma,%2C,dict.one.semi,%3B", + "list.one,list.two,list.three,dict.one.dot,.,dict.one.semi,%3B,dict.two.comma,%2C", + "list.one,list.two,list.three,dict.one.semi,%3B,dict.two.comma,%2C,dict.one.dot,.", + "list.one,list.two,list.three,dict.one.semi,%3B,dict.one.dot,.,dict.two.comma,%2C" + ]], + ["{mixed*}", [ + "list.one,list.two,list.three,dict.two.comma=%2C,dict.one.dot=.,dict.one.semi=%3B", + "list.one,list.two,list.three,dict.two.comma=%2C,dict.one.semi=%3B,dict.one.dot=.", + "list.one,list.two,list.three,dict.one.dot=.,dict.two.comma=%2C,dict.one.semi=%3B", + "list.one,list.two,list.three,dict.one.dot=.,dict.one.semi=%3B,dict.two.comma=%2C", + "list.one,list.two,list.three,dict.one.semi=%3B,dict.two.comma=%2C,dict.one.dot=.", + "list.one,list.two,list.three,dict.one.semi=%3B,dict.one.dot=.,dict.two.comma=%2C" + ]], + ["{?mixed}", [ + "?mixed=list.one,list.two,list.three,dict.two.comma,%2C,dict.one.dot,.,dict.one.semi,%3B", + "?mixed=list.one,list.two,list.three,dict.two.comma,%2C,dict.one.semi,%3B,dict.one.dot,.", + "?mixed=list.one,list.two,list.three,dict.one.dot,.,dict.two.comma,%2C,dict.one.semi,%3B", + "?mixed=list.one,list.two,list.three,dict.one.dot,.,dict.one.semi,%3B,dict.two.comma,%2C", + "?mixed=list.one,list.two,list.three,dict.one.semi,%3B,dict.two.comma,%2C,dict.one.dot,.", + "?mixed=list.one,list.two,list.three,dict.one.semi,%3B,dict.one.dot,.,dict.two.comma,%2C" + ]], + ["{?mixed*}", [ + "?list=one&list=two&list=three&dict.two.comma=%2C&dict.one.dot=.&dict.one.semi=%3B", + "?list=one&list=two&list=three&dict.two.comma,%2C&dict.one.semi=%3B&dict.one.dot=.", + "?list=one&list=two&list=three&dict.one.dot=.&dict.two.comma=%2C&dict.one.semi=%3B", + "?list=one&list=two&list=three&dict.one.dot=.&dict.one.semi=%3B&dict.two.comma=%2C", + "?list=one&list=two&list=three&dict.one.semi=%3B&dict.two.comma=%2C&dict.one.dot=.", + "?list=one&list=two&list=three&dict.one.semi=%3B&dict.one.dot=.&dict.two.comma=%2C" + ]], + ["{dlist}", [ + "dot,.,semi,%3B,comma,%2C", + "semi,%3B,dot,.,comma,%2C" + ]], + ["{dlist*}", [ + "dot=.,semi=%3B,comma=%2C", + "semi=%3B,dot=.,comma=%2C" + ]], + ["{?dlist}", [ + "?dlist=dot,.,semi,%3B,comma,%2C", + "?dlist=semi,%3B,dot,.,comma,%2C" + ]], + ["{?dlist*}", [ + "?dlist.dot=.&dlist.semi=%3B&dlist.comma=%2C", + "?dlist.semi=%3B&dlist.dot=.&dlist.comma=%2C" + ]] + ] + }, + "Array Modifier" : + { + "level": 5, + "variables": { + "list" : ["one", "two", "three", "four"], + "dict" : {"semi": ";", "dot": ".", "comma": ","}, + "lists" : [["one", "two"], ["three"], "four"], + "dicts" : {"one": {"semi": ";", "dot": "."}, "two": {"comma": ","}}, + "mixed" : {"list": ["one", ["two", "three"]], "dict": {"one": {"semi": ";", "dot": "."}, "two": {"comma": ","}}}, + "dlist" : [{"semi": ";", "dot": "."}, {"comma": ","}] + }, + "testcases" : [ + ["{list[]}", "one,two,three,four"], + ["{?list[]}", "?list[0]=one&list[1]=two&list[2]=three&list[3]=four"], + ["{lists[]}", "one,two,three,four"], + ["{?lists[]}", "?lists[0][0]=one&lists[0][1]=two&lists[1][0]=three&lists[2]=four"], + ["{dict[]}", [ + "comma=%2C,dot=.,semi=%3B", + "comma=%2C,semi=%3B,dot=.", + "dot=.,comma=%2C,semi=%3B", + "dot=.,semi=%3B,comma=%2C", + "semi=%3B,comma=%2C,dot=.", + "semi=%3B,dot=.,comma=%2C" + ]], + ["{?dict[]}", [ + "?comma=%2C&dot=.&semi=%3B", + "?comma=%2C&semi=%3B&dot=.", + "?dot=.&comma=%2C&semi=%3B", + "?dot=.&semi=%3B&comma=%2C", + "?semi=%3B&comma=%2C&dot=.", + "?semi=%3B&dot=.&comma=%2C" + ]], + ["{dicts[]}", [ + "two[comma]=%2C,one[dot]=.,one[semi]=%3B", + "two[comma]=%2C,one[semi]=%3B,one[dot]=.", + "one[dot]=.,two[comma]=%2C,one[semi]=%3B", + "one[dot]=.,one[semi]=%3B,two[comma]=%2C", + "one[semi]=%3B,two[comma]=%2C,one[dot]=.", + "one[semi]=%3B,one[dot]=.,two[comma]=%2C" + ]], + ["{?dicts[]}", [ + "?two[comma]=%2C&one[dot]=.&one[semi]=%3B", + "?two[comma]=%2C&one[semi]=%3B&one[dot]=.", + "?one[dot]=.&two[comma]=%2C&one[semi]=%3B", + "?one[dot]=.&one[semi]=%3B&two[comma]=%2C", + "?one[semi]=%3B&two[comma]=%2C&one[dot]=.", + "?one[semi]=%3B&one[dot]=.&two[comma]=%2C" + ]], + ["{mixed[]}", [ + "list[0]=one,list[1][0]=two,list[1][1]=three,dict[two][comma]=%2C,dict[one][dot]=.,dict[one][semi]=%3B", + "list[0]=one,list[1][0]=two,list[1][1]=three,dict[two][comma]=%2C,dict[one][semi]=%3B,dict[one][dot]=.", + "list[0]=one,list[1][0]=two,list[1][1]=three,dict[one][dot]=.,dict[two][comma]=%2C,dict[one][semi]=%3B", + "list[0]=one,list[1][0]=two,list[1][1]=three,dict[one][dot]=.,dict[one][semi]=%3B,dict[two][comma]=%2C", + "list[0]=one,list[1][0]=two,list[1][1]=three,dict[one][semi]=%3B,dict[two][comma]=%2C,dict[one][dot]=.", + "list[0]=one,list[1][0]=two,list[1][1]=three,dict[one][semi]=%3B,dict[one][dot]=.,dict[two][comma]=%2C" + ]], + ["{?mixed[]}", [ + "?list[0]=one&list[1][0]=two&list[1][1]=three&dict[two][comma]=%2C&dict[one][dot]=.&dict[one][semi]=%3B", + "?list[0]=one&list[1][0]=two&list[1][1]=three&dict[two][comma]=%2C&dict[one][semi]=%3B&dict[one][dot]=.", + "?list[0]=one&list[1][0]=two&list[1][1]=three&dict[one][dot]=.&dict[two][comma]=%2C&dict[one][semi]=%3B", + "?list[0]=one&list[1][0]=two&list[1][1]=three&dict[one][dot]=.&dict[one][semi]=%3B&dict[two][comma]=%2C", + "?list[0]=one&list[1][0]=two&list[1][1]=three&dict[one][semi]=%3B&dict[two][comma]=%2C&dict[one][dot]=.", + "?list[0]=one&list[1][0]=two&list[1][1]=three&dict[one][semi]=%3B&dict[one][dot]=.&dict[two][comma]=%2C" + ]], + ["{dlist[]}", [ + "dot=.,semi=%3B,comma=%2C", + "semi=%3B,dot=.,comma=%2C" + ]], + ["{?dlist[]}", [ + "?dlist[0][dot]=.&dlist[0][semi]=%3B&dlist[1][comma]=%2C", + "?dlist[0][semi]=%3B&dlist[0][dot]=.&dlist[1][comma]=%2C" + ]] + ] + } +} |