sdcvalidator 4.5.0__tar.gz → 4.5.2__tar.gz
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.
- {sdcvalidator-4.5.0 → sdcvalidator-4.5.2}/PKG-INFO +3 -3
- {sdcvalidator-4.5.0 → sdcvalidator-4.5.2}/pyproject.toml +3 -3
- {sdcvalidator-4.5.0 → sdcvalidator-4.5.2}/src/sdcvalidator/__init__.py +1 -1
- {sdcvalidator-4.5.0 → sdcvalidator-4.5.2}/src/sdcvalidator/mcp_server.py +23 -3
- {sdcvalidator-4.5.0 → sdcvalidator-4.5.2}/src/sdcvalidator/validator.py +50 -5
- {sdcvalidator-4.5.0 → sdcvalidator-4.5.2}/src/sdcvalidator.egg-info/PKG-INFO +3 -3
- {sdcvalidator-4.5.0 → sdcvalidator-4.5.2}/src/sdcvalidator.egg-info/SOURCES.txt +2 -1
- {sdcvalidator-4.5.0 → sdcvalidator-4.5.2}/tests/test_mcp_server.py +52 -0
- sdcvalidator-4.5.2/tests/test_xsi_type.py +60 -0
- {sdcvalidator-4.5.0 → sdcvalidator-4.5.2}/LICENSE +0 -0
- {sdcvalidator-4.5.0 → sdcvalidator-4.5.2}/README.md +0 -0
- {sdcvalidator-4.5.0 → sdcvalidator-4.5.2}/setup.cfg +0 -0
- {sdcvalidator-4.5.0 → sdcvalidator-4.5.2}/src/sdcvalidator/cli.py +0 -0
- {sdcvalidator-4.5.0 → sdcvalidator-4.5.2}/src/sdcvalidator/constants.py +0 -0
- {sdcvalidator-4.5.0 → sdcvalidator-4.5.2}/src/sdcvalidator/converters.py +0 -0
- {sdcvalidator-4.5.0 → sdcvalidator-4.5.2}/src/sdcvalidator/error_classifier.py +0 -0
- {sdcvalidator-4.5.0 → sdcvalidator-4.5.2}/src/sdcvalidator/error_mapper.py +0 -0
- {sdcvalidator-4.5.0 → sdcvalidator-4.5.2}/src/sdcvalidator/exceptions.py +0 -0
- {sdcvalidator-4.5.0 → sdcvalidator-4.5.2}/src/sdcvalidator/instance_modifier.py +0 -0
- {sdcvalidator-4.5.0 → sdcvalidator-4.5.2}/src/sdcvalidator/schema_checker.py +0 -0
- {sdcvalidator-4.5.0 → sdcvalidator-4.5.2}/src/sdcvalidator/xsd11_restriction.py +0 -0
- {sdcvalidator-4.5.0 → sdcvalidator-4.5.2}/src/sdcvalidator.egg-info/dependency_links.txt +0 -0
- {sdcvalidator-4.5.0 → sdcvalidator-4.5.2}/src/sdcvalidator.egg-info/entry_points.txt +0 -0
- {sdcvalidator-4.5.0 → sdcvalidator-4.5.2}/src/sdcvalidator.egg-info/requires.txt +0 -0
- {sdcvalidator-4.5.0 → sdcvalidator-4.5.2}/src/sdcvalidator.egg-info/top_level.txt +0 -0
- {sdcvalidator-4.5.0 → sdcvalidator-4.5.2}/tests/test_converters.py +0 -0
- {sdcvalidator-4.5.0 → sdcvalidator-4.5.2}/tests/test_error_classifier.py +0 -0
- {sdcvalidator-4.5.0 → sdcvalidator-4.5.2}/tests/test_error_mapper.py +0 -0
- {sdcvalidator-4.5.0 → sdcvalidator-4.5.2}/tests/test_instance_modifier.py +0 -0
- {sdcvalidator-4.5.0 → sdcvalidator-4.5.2}/tests/test_recovery.py +0 -0
- {sdcvalidator-4.5.0 → sdcvalidator-4.5.2}/tests/test_schema_checker.py +0 -0
- {sdcvalidator-4.5.0 → sdcvalidator-4.5.2}/tests/test_validator.py +0 -0
- {sdcvalidator-4.5.0 → sdcvalidator-4.5.2}/tests/test_xsd11_restriction.py +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sdcvalidator
|
|
3
|
-
Version: 4.5.
|
|
3
|
+
Version: 4.5.2
|
|
4
4
|
Summary: SDC4 structural validator with ExceptionalValue recovery — thin wrapper over xmlschema
|
|
5
|
-
Author:
|
|
5
|
+
Author: Axius SDC, Inc.
|
|
6
6
|
License-Expression: Apache-2.0
|
|
7
|
-
Classifier: Development Status ::
|
|
7
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
8
8
|
Classifier: Intended Audience :: Developers
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.10
|
|
@@ -4,16 +4,16 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sdcvalidator"
|
|
7
|
-
version = "4.5.
|
|
7
|
+
version = "4.5.2"
|
|
8
8
|
description = "SDC4 structural validator with ExceptionalValue recovery — thin wrapper over xmlschema"
|
|
9
9
|
license = "Apache-2.0"
|
|
10
10
|
requires-python = ">=3.10"
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
authors = [
|
|
13
|
-
{name = "
|
|
13
|
+
{name = "Axius SDC, Inc."},
|
|
14
14
|
]
|
|
15
15
|
classifiers = [
|
|
16
|
-
"Development Status ::
|
|
16
|
+
"Development Status :: 5 - Production/Stable",
|
|
17
17
|
"Intended Audience :: Developers",
|
|
18
18
|
"Programming Language :: Python :: 3",
|
|
19
19
|
"Programming Language :: Python :: 3.10",
|
|
@@ -14,7 +14,7 @@ Thin wrapper over xmlschema with two-tier error classification and the SDC4
|
|
|
14
14
|
"quarantine-and-tag" recovery pattern (ISO 21090 ExceptionalValues).
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
|
-
__version__ = "4.5.
|
|
17
|
+
__version__ = "4.5.2"
|
|
18
18
|
|
|
19
19
|
# etree_tostring is re-exported from xmlschema so callers can serialize the
|
|
20
20
|
# ElementTree returned by validate_with_recovery() without importing xmlschema
|
|
@@ -36,7 +36,7 @@ from sdcvalidator.schema_checker import validate_sdc4_schema_compliance
|
|
|
36
36
|
JSONRPC_VERSION = "2.0"
|
|
37
37
|
MCP_PROTOCOL_VERSION = "2024-11-05"
|
|
38
38
|
SERVER_NAME = "sdcvalidator"
|
|
39
|
-
SERVER_VERSION = "4.2
|
|
39
|
+
SERVER_VERSION = "4.5.2"
|
|
40
40
|
|
|
41
41
|
# Validator cache: schema_path -> SDC4Validator
|
|
42
42
|
_validators: dict[str, SDC4Validator] = {}
|
|
@@ -201,10 +201,24 @@ def _jsonrpc_error(id: Any, code: int, message: str, data: Any = None) -> dict:
|
|
|
201
201
|
return {"jsonrpc": JSONRPC_VERSION, "id": id, "error": error}
|
|
202
202
|
|
|
203
203
|
|
|
204
|
-
def _handle_request(request
|
|
204
|
+
def _handle_request(request) -> dict | None:
|
|
205
|
+
# ★ Attacker-reachable input over stdio with no authentication. A JSON
|
|
206
|
+
# value that is not an object, or an object whose params is not one,
|
|
207
|
+
# used to raise AttributeError and kill the server (VSL rollout R9).
|
|
208
|
+
# Every shape the protocol does not allow is answered with -32600.
|
|
209
|
+
if not isinstance(request, dict):
|
|
210
|
+
return _jsonrpc_error(None, -32600, "Invalid Request: a JSON-RPC request is an object")
|
|
211
|
+
req_id = request.get("id")
|
|
212
|
+
if not isinstance(req_id, (str, int, type(None))):
|
|
213
|
+
return _jsonrpc_error(None, -32600, "Invalid Request: id must be a string, a number or null")
|
|
205
214
|
method = request.get("method", "")
|
|
215
|
+
if not isinstance(method, str):
|
|
216
|
+
return _jsonrpc_error(req_id, -32600, "Invalid Request: method must be a string")
|
|
206
217
|
params = request.get("params", {})
|
|
207
|
-
|
|
218
|
+
if params is None:
|
|
219
|
+
params = {}
|
|
220
|
+
if not isinstance(params, dict):
|
|
221
|
+
return _jsonrpc_error(req_id, -32600, "Invalid Request: params must be an object")
|
|
208
222
|
|
|
209
223
|
if method == "initialize":
|
|
210
224
|
result = {
|
|
@@ -228,6 +242,12 @@ def _handle_request(request: dict) -> dict | None:
|
|
|
228
242
|
elif method == "tools/call":
|
|
229
243
|
tool_name = params.get("name", "")
|
|
230
244
|
tool_args = params.get("arguments", {})
|
|
245
|
+
if tool_args is None:
|
|
246
|
+
tool_args = {}
|
|
247
|
+
if not isinstance(tool_name, str) or not isinstance(tool_args, dict):
|
|
248
|
+
return _jsonrpc_error(
|
|
249
|
+
req_id, -32602, "Invalid params: name must be a string and arguments an object"
|
|
250
|
+
)
|
|
231
251
|
|
|
232
252
|
handler = TOOL_HANDLERS.get(tool_name)
|
|
233
253
|
if handler is None:
|
|
@@ -136,6 +136,51 @@ class SDC4Validator:
|
|
|
136
136
|
else:
|
|
137
137
|
raise TypeError(f"Unsupported xml_source type: {type(xml_source)}")
|
|
138
138
|
|
|
139
|
+
def _namespaces(
|
|
140
|
+
self, xml_source: Union[str, Path, ET.Element, XMLResource]
|
|
141
|
+
) -> Optional[Dict[str, str]]:
|
|
142
|
+
"""
|
|
143
|
+
The prefix map declared in the source, or None when it is not known.
|
|
144
|
+
|
|
145
|
+
``_to_tree`` normalises every source into a stdlib ElementTree, which
|
|
146
|
+
resolves prefixes to Clark notation and discards the declarations. A
|
|
147
|
+
prefixed QName that appears as *content* rather than as a tag, which
|
|
148
|
+
is what ``xsi:type="sdc4:XdOrdinalType"`` is, can then no longer be
|
|
149
|
+
resolved and xmlschema raises ``XMLSchemaKeyError`` instead of
|
|
150
|
+
validating. The reference model relies on ``xsi:type`` wherever an
|
|
151
|
+
abstract element is filled by a concrete type, so an instance that is
|
|
152
|
+
valid against the reference model failed here before 4.5.1.
|
|
153
|
+
|
|
154
|
+
A path or an XMLResource still carries the declarations, so they are
|
|
155
|
+
read back and handed to xmlschema. A bare ``ET.Element`` has already
|
|
156
|
+
lost them; callers validating such an instance should pass the path
|
|
157
|
+
or an ``XMLResource`` instead.
|
|
158
|
+
"""
|
|
159
|
+
if isinstance(xml_source, XMLResource):
|
|
160
|
+
return xml_source.get_namespaces(root_only=False)
|
|
161
|
+
if isinstance(xml_source, (str, Path)):
|
|
162
|
+
return XMLResource(str(xml_source)).get_namespaces(root_only=False)
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
def _schema_errors(
|
|
166
|
+
self,
|
|
167
|
+
tree: ET.ElementTree,
|
|
168
|
+
xml_source: Union[str, Path, ET.Element, XMLResource],
|
|
169
|
+
) -> Iterator[XMLSchemaValidationError]:
|
|
170
|
+
"""
|
|
171
|
+
xmlschema's errors over ``tree``, with the source's prefix map.
|
|
172
|
+
|
|
173
|
+
The map is for resolving prefixed QNames in content during validation.
|
|
174
|
+
It is removed from each error afterwards so ``error.path`` keeps its
|
|
175
|
+
Clark form (``/{uri}local/...``): the recovery step locates elements by
|
|
176
|
+
that form, and every report has always carried it.
|
|
177
|
+
"""
|
|
178
|
+
namespaces = self._namespaces(xml_source)
|
|
179
|
+
for error in self.schema.iter_errors(tree, namespaces=namespaces):
|
|
180
|
+
error.namespaces = None
|
|
181
|
+
error._path = None
|
|
182
|
+
yield error
|
|
183
|
+
|
|
139
184
|
# ------------------------------------------------------------------ #
|
|
140
185
|
# Classification-only validation (no modification)
|
|
141
186
|
# ------------------------------------------------------------------ #
|
|
@@ -150,7 +195,7 @@ class SDC4Validator:
|
|
|
150
195
|
ValidationResult with is_valid, structural_errors, semantic_errors.
|
|
151
196
|
"""
|
|
152
197
|
tree = self._to_tree(xml_source)
|
|
153
|
-
errors = list(self.
|
|
198
|
+
errors = list(self._schema_errors(tree, xml_source))
|
|
154
199
|
|
|
155
200
|
classified = self.classifier.classify_all(errors)
|
|
156
201
|
|
|
@@ -170,7 +215,7 @@ class SDC4Validator:
|
|
|
170
215
|
List of structural errors. Empty list means structure is valid.
|
|
171
216
|
"""
|
|
172
217
|
tree = self._to_tree(xml_source)
|
|
173
|
-
errors = list(self.
|
|
218
|
+
errors = list(self._schema_errors(tree, xml_source))
|
|
174
219
|
return [e for e in errors if self.classifier.is_structural_error(e)]
|
|
175
220
|
|
|
176
221
|
def iter_errors(
|
|
@@ -183,7 +228,7 @@ class SDC4Validator:
|
|
|
183
228
|
List of error summary dictionaries (structural and semantic).
|
|
184
229
|
"""
|
|
185
230
|
tree = self._to_tree(xml_source)
|
|
186
|
-
errors = list(self.
|
|
231
|
+
errors = list(self._schema_errors(tree, xml_source))
|
|
187
232
|
return [self.classifier.get_error_summary(e) for e in errors]
|
|
188
233
|
|
|
189
234
|
def iter_errors_with_mapping(
|
|
@@ -198,7 +243,7 @@ class SDC4Validator:
|
|
|
198
243
|
Dictionaries containing error details and the mapped EV type.
|
|
199
244
|
"""
|
|
200
245
|
tree = self._to_tree(xml_source)
|
|
201
|
-
for error in self.
|
|
246
|
+
for error in self._schema_errors(tree, xml_source):
|
|
202
247
|
ev_type = self.error_mapper.map_error(error)
|
|
203
248
|
if ev_type is None:
|
|
204
249
|
continue
|
|
@@ -286,7 +331,7 @@ class SDC4Validator:
|
|
|
286
331
|
if remove_existing_ev:
|
|
287
332
|
self.instance_modifier.remove_existing_exceptional_values(root)
|
|
288
333
|
|
|
289
|
-
errors = list(self.
|
|
334
|
+
errors = list(self._schema_errors(tree, xml_source))
|
|
290
335
|
|
|
291
336
|
structural_errors = []
|
|
292
337
|
semantic_errors = []
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sdcvalidator
|
|
3
|
-
Version: 4.5.
|
|
3
|
+
Version: 4.5.2
|
|
4
4
|
Summary: SDC4 structural validator with ExceptionalValue recovery — thin wrapper over xmlschema
|
|
5
|
-
Author:
|
|
5
|
+
Author: Axius SDC, Inc.
|
|
6
6
|
License-Expression: Apache-2.0
|
|
7
|
-
Classifier: Development Status ::
|
|
7
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
8
8
|
Classifier: Intended Audience :: Developers
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.10
|
|
@@ -137,3 +137,55 @@ class TestConsistentSerialization:
|
|
|
137
137
|
assert content[0]["type"] == "text"
|
|
138
138
|
parsed = json.loads(content[0]["text"])
|
|
139
139
|
assert parsed is not None
|
|
140
|
+
|
|
141
|
+
class TestMalformedRequestsDoNotKillTheServer:
|
|
142
|
+
"""
|
|
143
|
+
VSL rollout inventory R9. Six inputs that are valid JSON but not a
|
|
144
|
+
JSON-RPC request raised AttributeError in _handle_request and ended the
|
|
145
|
+
process. Each is now answered with -32600 (or -32602 for bad tool params)
|
|
146
|
+
and the loop keeps reading.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
@pytest.mark.parametrize("payload", [[], ["tools/list"], "tools/list", 7, None, True])
|
|
150
|
+
def test_a_non_object_request_is_invalid_request(self, payload):
|
|
151
|
+
response = _handle_request(payload)
|
|
152
|
+
assert response["error"]["code"] == -32600
|
|
153
|
+
assert response["id"] is None
|
|
154
|
+
|
|
155
|
+
@pytest.mark.parametrize("params", [[], "x", 3, True])
|
|
156
|
+
def test_non_object_params_are_invalid_request(self, params):
|
|
157
|
+
response = _handle_request({"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": params})
|
|
158
|
+
assert response["error"]["code"] == -32600
|
|
159
|
+
assert response["id"] == 1
|
|
160
|
+
|
|
161
|
+
def test_null_params_are_treated_as_empty(self):
|
|
162
|
+
response = _handle_request({"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": None})
|
|
163
|
+
assert "result" in response
|
|
164
|
+
|
|
165
|
+
def test_a_non_string_method_is_invalid_request(self):
|
|
166
|
+
response = _handle_request({"jsonrpc": "2.0", "id": 1, "method": ["tools/list"]})
|
|
167
|
+
assert response["error"]["code"] == -32600
|
|
168
|
+
|
|
169
|
+
@pytest.mark.parametrize("arguments", [[], "x", 3])
|
|
170
|
+
def test_non_object_tool_arguments_are_invalid_params(self, arguments):
|
|
171
|
+
response = _handle_request({
|
|
172
|
+
"jsonrpc": "2.0", "id": 1, "method": "tools/call",
|
|
173
|
+
"params": {"name": list(TOOL_HANDLERS)[0], "arguments": arguments},
|
|
174
|
+
})
|
|
175
|
+
assert response["error"]["code"] == -32602
|
|
176
|
+
|
|
177
|
+
def test_the_stdio_loop_survives_every_one_of_them(self, monkeypatch, capsys):
|
|
178
|
+
import io
|
|
179
|
+
import sys
|
|
180
|
+
|
|
181
|
+
from sdcvalidator.mcp_server import run_stdio
|
|
182
|
+
|
|
183
|
+
lines = ["[]", "\"tools/list\"", "7", "null",
|
|
184
|
+
json.dumps({"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": []}),
|
|
185
|
+
json.dumps({"jsonrpc": "2.0", "id": 2, "method": "ping"})]
|
|
186
|
+
monkeypatch.setattr(sys, "stdin", io.StringIO("\n".join(lines) + "\n"))
|
|
187
|
+
run_stdio()
|
|
188
|
+
out = [json.loads(l) for l in capsys.readouterr().out.strip().splitlines()]
|
|
189
|
+
assert len(out) == 6
|
|
190
|
+
assert [o.get("error", {}).get("code") for o in out[:5]] == [-32600] * 5
|
|
191
|
+
assert out[5] == {"jsonrpc": "2.0", "id": 2, "result": {}}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Instances that fill a declared type through ``xsi:type``.
|
|
3
|
+
|
|
4
|
+
The SDC4 reference model declares ``sdc4:XdOrdinal`` and its siblings
|
|
5
|
+
abstract, so a schema-valid instance fills them with a concrete type through
|
|
6
|
+
``xsi:type="sdc4:XdOrdinalType"``. Before 4.5.1 every input was normalised
|
|
7
|
+
into a stdlib ElementTree, which drops the namespace prefix declarations;
|
|
8
|
+
the prefixed QName in ``xsi:type`` then could not be resolved and xmlschema
|
|
9
|
+
raised ``XMLSchemaKeyError`` instead of validating. A path or an XMLResource
|
|
10
|
+
carries the declarations and must validate.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import io
|
|
14
|
+
import unittest
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from xmlschema import XMLResource
|
|
18
|
+
|
|
19
|
+
from sdcvalidator.validator import SDC4Validator
|
|
20
|
+
|
|
21
|
+
DATA = Path(__file__).parent / "test_data"
|
|
22
|
+
SCHEMA = DATA / "xsi_type_schema.xsd"
|
|
23
|
+
VALID = DATA / "xsi_type_instance.xml"
|
|
24
|
+
INVALID = DATA / "xsi_type_instance_invalid.xml"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TestXsiTypeResolution(unittest.TestCase):
|
|
28
|
+
def setUp(self):
|
|
29
|
+
self.validator = SDC4Validator(SCHEMA, check_sdc4_compliance=False)
|
|
30
|
+
|
|
31
|
+
def test_a_path_validates(self):
|
|
32
|
+
result = self.validator.validate(str(VALID))
|
|
33
|
+
self.assertTrue(result.is_valid, result.structural_errors + result.semantic_errors)
|
|
34
|
+
|
|
35
|
+
def test_a_pathlib_path_validates(self):
|
|
36
|
+
self.assertTrue(self.validator.validate(VALID).is_valid)
|
|
37
|
+
|
|
38
|
+
def test_an_xmlresource_over_bytes_validates(self):
|
|
39
|
+
resource = XMLResource(io.BytesIO(VALID.read_bytes()))
|
|
40
|
+
self.assertTrue(self.validator.validate(resource).is_valid)
|
|
41
|
+
|
|
42
|
+
def test_the_report_and_recovery_paths_resolve_the_type_too(self):
|
|
43
|
+
report = self.validator.validate_and_report(str(VALID))
|
|
44
|
+
self.assertTrue(report["valid"], report)
|
|
45
|
+
self.assertEqual(report["error_count"], 0)
|
|
46
|
+
self.validator.validate_with_recovery(str(VALID), save=False)
|
|
47
|
+
self.assertEqual(self.validator.validate_structure(str(VALID)), [])
|
|
48
|
+
self.assertEqual(self.validator.iter_errors(str(VALID)), [])
|
|
49
|
+
|
|
50
|
+
def test_a_type_that_resolves_is_still_checked(self):
|
|
51
|
+
"""Resolving the type must not mean skipping it: a bad ordinal is caught."""
|
|
52
|
+
result = self.validator.validate(str(INVALID))
|
|
53
|
+
self.assertFalse(result.is_valid)
|
|
54
|
+
self.assertEqual(result.error_count, 1)
|
|
55
|
+
report = self.validator.validate_and_report(XMLResource(io.BytesIO(INVALID.read_bytes())))
|
|
56
|
+
self.assertEqual(report["error_count"], 1)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
if __name__ == "__main__":
|
|
60
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|