django-gisserver 1.5.0__py3-none-any.whl → 2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {django_gisserver-1.5.0.dist-info → django_gisserver-2.0.dist-info}/METADATA +14 -4
- django_gisserver-2.0.dist-info/RECORD +66 -0
- {django_gisserver-1.5.0.dist-info → django_gisserver-2.0.dist-info}/WHEEL +1 -1
- gisserver/__init__.py +1 -1
- gisserver/compat.py +23 -0
- gisserver/conf.py +7 -0
- gisserver/db.py +56 -47
- gisserver/exceptions.py +26 -2
- gisserver/extensions/__init__.py +4 -0
- gisserver/{parsers/fes20 → extensions}/functions.py +10 -4
- gisserver/extensions/queries.py +261 -0
- gisserver/features.py +220 -156
- gisserver/geometries.py +32 -37
- gisserver/management/__init__.py +0 -0
- gisserver/management/commands/__init__.py +0 -0
- gisserver/management/commands/loadgeojson.py +291 -0
- gisserver/operations/base.py +122 -308
- gisserver/operations/wfs20.py +423 -337
- gisserver/output/__init__.py +9 -48
- gisserver/output/base.py +178 -139
- gisserver/output/csv.py +65 -74
- gisserver/output/geojson.py +34 -35
- gisserver/output/gml32.py +254 -246
- gisserver/output/iters.py +207 -0
- gisserver/output/results.py +52 -26
- gisserver/output/stored.py +143 -0
- gisserver/output/utils.py +75 -170
- gisserver/output/xmlschema.py +85 -46
- gisserver/parsers/__init__.py +10 -10
- gisserver/parsers/ast.py +320 -0
- gisserver/parsers/fes20/__init__.py +13 -27
- gisserver/parsers/fes20/expressions.py +82 -38
- gisserver/parsers/fes20/filters.py +111 -43
- gisserver/parsers/fes20/identifiers.py +44 -26
- gisserver/parsers/fes20/lookups.py +144 -0
- gisserver/parsers/fes20/operators.py +331 -127
- gisserver/parsers/fes20/sorting.py +104 -33
- gisserver/parsers/gml/__init__.py +12 -11
- gisserver/parsers/gml/base.py +5 -2
- gisserver/parsers/gml/geometries.py +69 -35
- gisserver/parsers/ows/__init__.py +25 -0
- gisserver/parsers/ows/kvp.py +190 -0
- gisserver/parsers/ows/requests.py +158 -0
- gisserver/parsers/query.py +175 -0
- gisserver/parsers/values.py +26 -0
- gisserver/parsers/wfs20/__init__.py +37 -0
- gisserver/parsers/wfs20/adhoc.py +245 -0
- gisserver/parsers/wfs20/base.py +143 -0
- gisserver/parsers/wfs20/projection.py +103 -0
- gisserver/parsers/wfs20/requests.py +482 -0
- gisserver/parsers/wfs20/stored.py +192 -0
- gisserver/parsers/xml.py +249 -0
- gisserver/projection.py +357 -0
- gisserver/static/gisserver/index.css +12 -1
- gisserver/templates/gisserver/index.html +1 -1
- gisserver/templates/gisserver/service_description.html +2 -2
- gisserver/templates/gisserver/wfs/2.0.0/get_capabilities.xml +9 -9
- gisserver/templates/gisserver/wfs/feature_field.html +2 -2
- gisserver/templatetags/gisserver_tags.py +20 -0
- gisserver/types.py +322 -259
- gisserver/views.py +198 -56
- django_gisserver-1.5.0.dist-info/RECORD +0 -54
- gisserver/parsers/base.py +0 -149
- gisserver/parsers/fes20/query.py +0 -285
- gisserver/parsers/tags.py +0 -102
- gisserver/queries/__init__.py +0 -37
- gisserver/queries/adhoc.py +0 -185
- gisserver/queries/base.py +0 -186
- gisserver/queries/projection.py +0 -240
- gisserver/queries/stored.py +0 -206
- gisserver/templates/gisserver/wfs/2.0.0/describe_stored_queries.xml +0 -20
- gisserver/templates/gisserver/wfs/2.0.0/list_stored_queries.xml +0 -14
- {django_gisserver-1.5.0.dist-info → django_gisserver-2.0.dist-info}/LICENSE +0 -0
- {django_gisserver-1.5.0.dist-info → django_gisserver-2.0.dist-info}/top_level.txt +0 -0
gisserver/output/utils.py
CHANGED
|
@@ -1,179 +1,84 @@
|
|
|
1
|
-
|
|
1
|
+
"""General utilities for outputting XML content"""
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
from
|
|
5
|
-
from itertools import islice
|
|
6
|
-
from typing import TypeVar
|
|
3
|
+
from datetime import date, datetime, time, timezone
|
|
4
|
+
from decimal import Decimal as D
|
|
7
5
|
|
|
8
|
-
from django.
|
|
9
|
-
from lru import LRU
|
|
6
|
+
from django.core.exceptions import ImproperlyConfigured
|
|
10
7
|
|
|
11
|
-
|
|
8
|
+
from gisserver.parsers.xml import xmlns
|
|
12
9
|
|
|
13
|
-
|
|
10
|
+
AUTO_STR = (int, float, D, date, time)
|
|
14
11
|
|
|
12
|
+
COMMON_NAMESPACES = xmlns.as_namespaces()
|
|
15
13
|
|
|
16
|
-
class CountingIterator(Iterable[M]):
|
|
17
|
-
"""A simple iterator that counts how many results are given."""
|
|
18
14
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
15
|
+
__all__ = (
|
|
16
|
+
"attr_escape",
|
|
17
|
+
"tag_escape",
|
|
18
|
+
"to_qname",
|
|
19
|
+
"render_xmlns_attributes",
|
|
20
|
+
"value_to_text",
|
|
21
|
+
"value_to_xml_string",
|
|
22
|
+
)
|
|
25
23
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
return
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
used when server-side cursors are not available. The default follows Django behavior.
|
|
79
|
-
"""
|
|
80
|
-
self.queryset = queryset
|
|
81
|
-
self.sql_chunk_size = sql_chunk_size or DEFAULT_SQL_CHUNK_SIZE
|
|
82
|
-
self.chunk_size = chunk_size or self.sql_chunk_size
|
|
83
|
-
self._fk_caches = defaultdict(lambda: LRU(self.chunk_size // 2))
|
|
84
|
-
self._number_returned = 0
|
|
85
|
-
self._in_iterator = False
|
|
86
|
-
|
|
87
|
-
def __iter__(self):
|
|
88
|
-
# Using iter() ensures the ModelIterable is resumed with the next chunk.
|
|
89
|
-
self._number_returned = 0
|
|
90
|
-
self._in_iterator = True
|
|
24
|
+
|
|
25
|
+
def tag_escape(s: str):
|
|
26
|
+
return s.replace("&", "&").replace("<", "<").replace(">", ">")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def attr_escape(s: str):
|
|
30
|
+
# Slightly faster then html.escape() as it doesn't replace single quotes.
|
|
31
|
+
# Having tried all possible variants, this code still outperforms other forms of escaping.
|
|
32
|
+
return s.replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def value_to_xml_string(value):
|
|
36
|
+
# Simple scalar value
|
|
37
|
+
if isinstance(value, str): # most cases
|
|
38
|
+
return tag_escape(value)
|
|
39
|
+
elif isinstance(value, datetime):
|
|
40
|
+
return value.astimezone(timezone.utc).isoformat()
|
|
41
|
+
elif isinstance(value, bool):
|
|
42
|
+
return "true" if value else "false"
|
|
43
|
+
elif isinstance(value, AUTO_STR):
|
|
44
|
+
return value # no need for _tag_escape(), and f"{value}" works faster.
|
|
45
|
+
else:
|
|
46
|
+
return tag_escape(str(value))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def value_to_text(value):
|
|
50
|
+
# Simple scalar value, no XML escapes
|
|
51
|
+
if isinstance(value, str): # most cases
|
|
52
|
+
return value
|
|
53
|
+
elif isinstance(value, datetime):
|
|
54
|
+
return value.astimezone(timezone.utc).isoformat()
|
|
55
|
+
elif isinstance(value, bool):
|
|
56
|
+
return "true" if value else "false"
|
|
57
|
+
else:
|
|
58
|
+
return value # f"{value} works faster and produces the right format.
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def render_xmlns_attributes(xml_namespaces: dict[str, str]):
|
|
62
|
+
"""Render XML Namespace declaration attributes"""
|
|
63
|
+
return " ".join(
|
|
64
|
+
f'xmlns:{prefix}="{xml_namespace}"' if prefix else f'xmlns="{xml_namespace}"'
|
|
65
|
+
for xml_namespace, prefix in xml_namespaces.items()
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def to_qname(namespace, localname, namespaces: dict[str, str]) -> str:
|
|
70
|
+
"""Convert a fully qualified XML tag name to a prefixed short name."""
|
|
71
|
+
if namespace is None:
|
|
72
|
+
return localname
|
|
73
|
+
|
|
74
|
+
prefix = namespaces.get(namespace) # allow ""
|
|
75
|
+
if prefix is None:
|
|
91
76
|
try:
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
# Perform prefetches on this chunk:
|
|
101
|
-
if self.queryset._prefetch_related_lookups:
|
|
102
|
-
self._add_prefetches(instances)
|
|
103
|
-
|
|
104
|
-
# And return to parent loop
|
|
105
|
-
yield from instances
|
|
106
|
-
self._number_returned += len(instances)
|
|
107
|
-
finally:
|
|
108
|
-
self._in_iterator = False
|
|
109
|
-
|
|
110
|
-
def _get_queryset_iterator(self) -> Iterable:
|
|
111
|
-
"""The body of queryset.iterator(), while circumventing prefetching."""
|
|
112
|
-
# The old code did return `self.queryset.iterator(chunk_size=self.sql_chunk_size)`
|
|
113
|
-
# However, Django 4 supports using prefetch_related() with iterator() in that scenario.
|
|
114
|
-
#
|
|
115
|
-
# This code is the core of Django's QuerySet.iterator() that only produces the
|
|
116
|
-
# old-style iteration, without any prefetches. Those are added by this class instead.
|
|
117
|
-
use_chunked_fetch = not connections[self.queryset.db].settings_dict.get(
|
|
118
|
-
"DISABLE_SERVER_SIDE_CURSORS"
|
|
119
|
-
)
|
|
120
|
-
iterable = self.queryset._iterable_class(
|
|
121
|
-
self.queryset, chunked_fetch=use_chunked_fetch, chunk_size=self.sql_chunk_size
|
|
122
|
-
)
|
|
123
|
-
|
|
124
|
-
yield from iterable
|
|
125
|
-
|
|
126
|
-
@property
|
|
127
|
-
def number_returned(self) -> int:
|
|
128
|
-
"""Tell how many objects the iterator processed"""
|
|
129
|
-
if self._in_iterator:
|
|
130
|
-
raise RuntimeError("Can't read number of returned results during iteration")
|
|
131
|
-
return self._number_returned
|
|
132
|
-
|
|
133
|
-
def _add_prefetches(self, instances: list[M]):
|
|
134
|
-
"""Merge the prefetched objects for this batch with the model instances."""
|
|
135
|
-
if self._fk_caches:
|
|
136
|
-
# Make sure prefetch_related_objects() doesn't have
|
|
137
|
-
# to fetch items again that infrequently changes.
|
|
138
|
-
all_restored = self._restore_caches(instances)
|
|
139
|
-
if all_restored:
|
|
140
|
-
return
|
|
141
|
-
|
|
142
|
-
# Reuse the Django machinery for retrieving missing sub objects.
|
|
143
|
-
# and analyse the ForeignKey caches to allow faster prefetches next time
|
|
144
|
-
models.prefetch_related_objects(instances, *self.queryset._prefetch_related_lookups)
|
|
145
|
-
self._persist_prefetch_cache(instances)
|
|
146
|
-
|
|
147
|
-
def _persist_prefetch_cache(self, instances):
|
|
148
|
-
"""Store the prefetched data so it can be applied to the next batch"""
|
|
149
|
-
for instance in instances:
|
|
150
|
-
for lookup, obj in instance._state.fields_cache.items():
|
|
151
|
-
if obj is not None:
|
|
152
|
-
cache = self._fk_caches[lookup]
|
|
153
|
-
cache[obj.pk] = obj
|
|
154
|
-
|
|
155
|
-
def _restore_caches(self, instances) -> bool:
|
|
156
|
-
"""Restore prefetched data to the new set of instances.
|
|
157
|
-
This avoids unneeded prefetching of the same ForeignKey relation.
|
|
158
|
-
"""
|
|
159
|
-
if not instances:
|
|
160
|
-
return True
|
|
161
|
-
if not self._fk_caches:
|
|
162
|
-
return False
|
|
163
|
-
|
|
164
|
-
all_restored = True
|
|
165
|
-
|
|
166
|
-
for lookup, cache in self._fk_caches.items():
|
|
167
|
-
field = instances[0]._meta.get_field(lookup)
|
|
168
|
-
for instance in instances:
|
|
169
|
-
id_value = getattr(instance, field.attname)
|
|
170
|
-
if id_value is None:
|
|
171
|
-
continue
|
|
172
|
-
|
|
173
|
-
obj = cache.get(id_value, None)
|
|
174
|
-
if obj is not None:
|
|
175
|
-
instance._state.fields_cache[lookup] = obj
|
|
176
|
-
else:
|
|
177
|
-
all_restored = False
|
|
178
|
-
|
|
179
|
-
return all_restored
|
|
77
|
+
prefix = COMMON_NAMESPACES[namespace]
|
|
78
|
+
except KeyError:
|
|
79
|
+
raise ImproperlyConfigured(
|
|
80
|
+
f"No XML namespace prefix defined for '{namespace}'.\n"
|
|
81
|
+
"This can be configured in 'WFSView.xml_namespace_aliases'."
|
|
82
|
+
) from None
|
|
83
|
+
|
|
84
|
+
return f"{prefix}:{localname}" if prefix else localname
|
gisserver/output/xmlschema.py
CHANGED
|
@@ -1,47 +1,62 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import typing
|
|
3
4
|
from collections import deque
|
|
4
5
|
from collections.abc import Iterable
|
|
5
6
|
from io import StringIO
|
|
6
7
|
from typing import cast
|
|
7
8
|
|
|
8
9
|
from gisserver.features import FeatureType
|
|
9
|
-
from gisserver.
|
|
10
|
-
from gisserver.types import XsdComplexType
|
|
10
|
+
from gisserver.parsers.xml import xmlns
|
|
11
|
+
from gisserver.types import XsdComplexType, XsdElement
|
|
11
12
|
|
|
12
|
-
from .base import
|
|
13
|
+
from .base import XmlOutputRenderer
|
|
14
|
+
from .utils import to_qname
|
|
13
15
|
|
|
16
|
+
if typing.TYPE_CHECKING:
|
|
17
|
+
from gisserver.operations.base import WFSOperation
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
|
|
20
|
+
class XmlSchemaRenderer(XmlOutputRenderer):
|
|
16
21
|
"""Output rendering for DescribeFeatureType.
|
|
17
22
|
|
|
18
23
|
This renders a valid XSD schema that describes the data type of the feature.
|
|
19
24
|
"""
|
|
20
25
|
|
|
21
26
|
content_type = "application/gml+xml; version=3.2" # mandatory for WFS
|
|
27
|
+
xml_namespaces = {
|
|
28
|
+
# Default extra namespaces to include in the xmlns="..." attributes
|
|
29
|
+
"http://www.w3.org/2001/XMLSchema": "", # no xs:string, but "string"
|
|
30
|
+
"http://www.opengis.net/gml/3.2": "gml",
|
|
31
|
+
}
|
|
22
32
|
|
|
23
|
-
def __init__(self,
|
|
33
|
+
def __init__(self, operation: WFSOperation, feature_types: list[FeatureType]):
|
|
24
34
|
"""Overwritten method to handle the DescribeFeatureType context."""
|
|
25
|
-
|
|
26
|
-
self.app_xml_namespace = method.view.xml_namespace
|
|
35
|
+
super().__init__(operation)
|
|
27
36
|
self.feature_types = feature_types
|
|
28
37
|
|
|
38
|
+
# For rendering type="..." fields, avoid xs: prefix.
|
|
39
|
+
self.type_namespaces = self.app_namespaces.copy()
|
|
40
|
+
self.type_namespaces[xmlns.xs.value] = "" # no "xs:string" but "string"
|
|
41
|
+
|
|
42
|
+
def get_headers(self):
|
|
43
|
+
"""Make wget output slightly nicer."""
|
|
44
|
+
typenames = "+".join(feature_type.name for feature_type in self.feature_types)
|
|
45
|
+
return {"Content-Disposition": f'inline; filename="{typenames}.xsd"'}
|
|
46
|
+
|
|
29
47
|
def render_stream(self):
|
|
30
|
-
#
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
48
|
+
# Render first with original app_namespaces
|
|
49
|
+
xmlns_attrib = self.render_xmlns_attributes()
|
|
50
|
+
|
|
51
|
+
# Allow targetNamespace to be unprefixed by self.node_to_qname().
|
|
52
|
+
target_namespace = self.feature_types[0].xml_namespace
|
|
53
|
+
self.app_namespaces[target_namespace] = "" # no "app:element" but "element"
|
|
36
54
|
|
|
37
|
-
output = StringIO()
|
|
55
|
+
self.output = output = StringIO()
|
|
38
56
|
output.write(
|
|
39
57
|
f"""<?xml version='1.0' encoding="UTF-8" ?>
|
|
40
|
-
<schema
|
|
41
|
-
|
|
42
|
-
{xmlns_features}
|
|
43
|
-
xmlns:gml="http://www.opengis.net/gml/3.2"
|
|
44
|
-
targetNamespace="{self.app_xml_namespace}"
|
|
58
|
+
<schema {xmlns_attrib}
|
|
59
|
+
targetNamespace="{target_namespace}"
|
|
45
60
|
elementFormDefault="qualified" version="0.1">
|
|
46
61
|
|
|
47
62
|
"""
|
|
@@ -50,7 +65,7 @@ class XMLSchemaRenderer(OutputRenderer):
|
|
|
50
65
|
output.write("\n")
|
|
51
66
|
|
|
52
67
|
for feature_type in self.feature_types:
|
|
53
|
-
|
|
68
|
+
self.write_feature_type(feature_type)
|
|
54
69
|
|
|
55
70
|
output.write("</schema>\n")
|
|
56
71
|
return output.getvalue()
|
|
@@ -61,45 +76,69 @@ class XMLSchemaRenderer(OutputRenderer):
|
|
|
61
76
|
' schemaLocation="http://schemas.opengis.net/gml/3.2.1/gml.xsd" />\n'
|
|
62
77
|
)
|
|
63
78
|
|
|
64
|
-
def
|
|
65
|
-
output = StringIO()
|
|
79
|
+
def write_feature_type(self, feature_type: FeatureType):
|
|
66
80
|
xsd_type: XsdComplexType = feature_type.xsd_type
|
|
67
81
|
|
|
68
82
|
# This declares the that a top-level <app:featureName> is a class of a type.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
83
|
+
xsd_type_qname = self.to_qname(xsd_type, self.type_namespaces)
|
|
84
|
+
feature_qname = to_qname(
|
|
85
|
+
feature_type.xml_namespace, feature_type.name, self.app_namespaces
|
|
86
|
+
)
|
|
87
|
+
self.output.write(
|
|
88
|
+
f' <element name="{feature_qname}"'
|
|
89
|
+
f' type="{xsd_type_qname}" substitutionGroup="gml:AbstractFeature" />\n\n'
|
|
72
90
|
)
|
|
73
91
|
|
|
74
92
|
# Next, the complexType is rendered that defines the element contents.
|
|
75
93
|
# Next, the complexType(s) are rendered that defines the element contents.
|
|
76
94
|
# In case any fields are expanded (hence become subtypes), these are also included.
|
|
77
|
-
|
|
95
|
+
self.write_complex_type(xsd_type)
|
|
78
96
|
for complex_type in self._get_complex_types(xsd_type):
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return output.getvalue()
|
|
97
|
+
self.write_complex_type(complex_type)
|
|
82
98
|
|
|
83
|
-
def
|
|
99
|
+
def write_complex_type(self, complex_type: XsdComplexType):
|
|
84
100
|
"""Write the definition of a single class."""
|
|
85
|
-
|
|
86
|
-
output.write(
|
|
87
|
-
f' <complexType name="{complex_type.name}">\n'
|
|
88
|
-
" <complexContent>\n"
|
|
89
|
-
f' <extension base="{complex_type.base}">\n'
|
|
90
|
-
" <sequence>\n"
|
|
91
|
-
)
|
|
101
|
+
complex_qname = self.to_qname(complex_type)
|
|
102
|
+
self.output.write(f' <complexType name="{complex_qname}">\n')
|
|
92
103
|
|
|
93
|
-
|
|
94
|
-
|
|
104
|
+
if complex_type.base is not None:
|
|
105
|
+
base_qname = self.to_qname(complex_type.base)
|
|
106
|
+
self.output.write(
|
|
107
|
+
f" <complexContent>\n" # extend base class
|
|
108
|
+
f' <extension base="{base_qname}">\n'
|
|
109
|
+
)
|
|
110
|
+
indent = " "
|
|
111
|
+
else:
|
|
112
|
+
indent = ""
|
|
95
113
|
|
|
96
|
-
output.write(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
"
|
|
100
|
-
|
|
101
|
-
)
|
|
102
|
-
|
|
114
|
+
self.output.write(f" {indent}<sequence>\n")
|
|
115
|
+
|
|
116
|
+
for xsd_element in complex_type.elements:
|
|
117
|
+
self.output.write(f" {indent}{self.render_element(xsd_element)}\n")
|
|
118
|
+
|
|
119
|
+
self.output.write(f" {indent}</sequence>\n")
|
|
120
|
+
if complex_type.base is not None:
|
|
121
|
+
self.output.write(
|
|
122
|
+
" </extension>\n" # end extension
|
|
123
|
+
" </complexContent>\n"
|
|
124
|
+
)
|
|
125
|
+
self.output.write(" </complexType>\n\n")
|
|
126
|
+
|
|
127
|
+
def render_element(self, xsd_element: XsdElement):
|
|
128
|
+
"""Staticmethod for unit testing."""
|
|
129
|
+
qname = self.to_qname(xsd_element)
|
|
130
|
+
type_qname = self.to_qname(xsd_element.type, self.type_namespaces)
|
|
131
|
+
|
|
132
|
+
attributes = [f'name="{qname}" type="{type_qname}"']
|
|
133
|
+
if xsd_element.min_occurs is not None:
|
|
134
|
+
attributes.append(f'minOccurs="{xsd_element.min_occurs}"')
|
|
135
|
+
if xsd_element.max_occurs is not None:
|
|
136
|
+
attributes.append(f'maxOccurs="{xsd_element.max_occurs}"')
|
|
137
|
+
if xsd_element.nillable:
|
|
138
|
+
str_bool = "true" if xsd_element.nillable else "false"
|
|
139
|
+
attributes.append(f'nillable="{str_bool}"')
|
|
140
|
+
|
|
141
|
+
return "<element {} />".format(" ".join(attributes))
|
|
103
142
|
|
|
104
143
|
def _get_complex_types(self, root: XsdComplexType) -> Iterable[XsdComplexType]:
|
|
105
144
|
"""Find all fields that reference to complex types, including nested elements."""
|
gisserver/parsers/__init__.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
"""All parser logic to process
|
|
1
|
+
"""All parser logic to process incoming XML data.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
from .fes20 import function_registry as fes_function_registry
|
|
5
|
-
from .gml import parse_gml
|
|
3
|
+
This handles all tags, including:
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
* ``<wfs:...>``
|
|
6
|
+
* ``<fes:...>``
|
|
7
|
+
* ``<gml:...>``
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
Internally, the XML string is translated into an Abstract Syntax Tree (AST).
|
|
10
|
+
These objects contain to logic to process each bit of the XML tree.
|
|
11
|
+
The GET request parameters are translated into that same tree structure,
|
|
12
|
+
to have a uniform processing of the request.
|
|
13
|
+
"""
|