robotframework-xmlvalidator 1.0.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.
- robotframework_xmlvalidator-1.0.0.dist-info/LICENSE +201 -0
- robotframework_xmlvalidator-1.0.0.dist-info/METADATA +686 -0
- robotframework_xmlvalidator-1.0.0.dist-info/RECORD +8 -0
- robotframework_xmlvalidator-1.0.0.dist-info/WHEEL +4 -0
- xmlvalidator/XmlValidator.py +1605 -0
- xmlvalidator/__init__.py +60 -0
- xmlvalidator/xml_validator_results.py +413 -0
- xmlvalidator/xml_validator_utils.py +484 -0
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
# Copyright 2024-2025 Michael Hallik
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
12
|
+
# implied.
|
|
13
|
+
#
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
"""
|
|
19
|
+
Provides utility functions to support XML and XSD validation tasks.
|
|
20
|
+
|
|
21
|
+
This module is used internally by the XmlValidator library to assist
|
|
22
|
+
with file resolution, namespace extraction, validation sanity checks,
|
|
23
|
+
and schema matching.
|
|
24
|
+
|
|
25
|
+
It does not interact with Robot Framework directly, but supports the
|
|
26
|
+
functionality exposed by the main library class.
|
|
27
|
+
|
|
28
|
+
Features:
|
|
29
|
+
|
|
30
|
+
- Extraction of XML namespaces from parsed documents.
|
|
31
|
+
- Resolution and validation of file and directory paths.
|
|
32
|
+
- Well-formedness checks for XML and XSD files.
|
|
33
|
+
- Mapping of XML namespaces to matching XSD schemas.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# pylint: disable=I1101:c-extension-no-member
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
from __future__ import annotations
|
|
41
|
+
# Standard library imports.
|
|
42
|
+
from pathlib import Path
|
|
43
|
+
from typing import List, Optional, Tuple, Union, TYPE_CHECKING
|
|
44
|
+
# Third party library imports.
|
|
45
|
+
from lxml import etree
|
|
46
|
+
from robot.api import logger
|
|
47
|
+
# Local application imports.
|
|
48
|
+
from .xml_validator_results import ValidatorResult
|
|
49
|
+
if TYPE_CHECKING:
|
|
50
|
+
from xmlschema import XMLSchema
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ValidatorUtils:
|
|
54
|
+
"""
|
|
55
|
+
A stateless utility class for common XML and XSD validation
|
|
56
|
+
support operations.
|
|
57
|
+
|
|
58
|
+
`ValidatorUtils` provides reusable static methods used internally by
|
|
59
|
+
the `XmlValidator` class and its supporting modules. It handles
|
|
60
|
+
tasks such as:
|
|
61
|
+
|
|
62
|
+
- Extracting namespaces from XML documents.
|
|
63
|
+
- Resolving and validating file and directory paths.
|
|
64
|
+
- Performing well-formedness and sanity checks on files.
|
|
65
|
+
- Matching XML namespaces to candidate XSD schemas.
|
|
66
|
+
|
|
67
|
+
All methods are static and the class maintains no internal state.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
__version__ = '1.0.0'
|
|
71
|
+
|
|
72
|
+
@staticmethod
|
|
73
|
+
def _resolve_path(path: str | Path) -> Path:
|
|
74
|
+
"""
|
|
75
|
+
Resolves a file or directory path to an absolute `Path` object.
|
|
76
|
+
|
|
77
|
+
This internal helper method accepts either a string or a `Path`
|
|
78
|
+
instance and returns a resolved absolute path.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
|
|
82
|
+
- path (str or Path):
|
|
83
|
+
A relative or absolute file or folder path.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Path:
|
|
87
|
+
The resolved absolute path.
|
|
88
|
+
|
|
89
|
+
Notes:
|
|
90
|
+
|
|
91
|
+
- This method does not check for file existence or permissions.
|
|
92
|
+
- Used internally to normalize paths in validation workflows.
|
|
93
|
+
"""
|
|
94
|
+
resolved_path = Path(path).resolve() if isinstance(path, str) else path.resolve()
|
|
95
|
+
return resolved_path
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def extract_xml_namespaces(
|
|
99
|
+
xml_root: etree.ElementBase,
|
|
100
|
+
return_dict: Optional[bool] = False,
|
|
101
|
+
include_nested: Optional[bool] = False
|
|
102
|
+
) -> Union[
|
|
103
|
+
set[str],
|
|
104
|
+
dict[str | None, str]
|
|
105
|
+
]:
|
|
106
|
+
"""
|
|
107
|
+
Extracts XML namespaces from an XML root element.
|
|
108
|
+
|
|
109
|
+
This method retrieves namespaces declared in the `xmlns`
|
|
110
|
+
attributes of the XML document.
|
|
111
|
+
|
|
112
|
+
Namespaces can be returned as:
|
|
113
|
+
|
|
114
|
+
- A *set* of namespace URIs (default).
|
|
115
|
+
- A *dictionary* mapping prefixes to URIs (`return_dict=True`).
|
|
116
|
+
|
|
117
|
+
The method can optionally search nested elements for additional
|
|
118
|
+
namespace declarations (`include_nested=True`).
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
|
|
122
|
+
- xml_root (etree.ElementBase):
|
|
123
|
+
The root element of the parsed XML document.
|
|
124
|
+
- return_dict (bool, optional):
|
|
125
|
+
If True, returns a dict mapping namespace prefixes to URIs.
|
|
126
|
+
If False, returns a set of URIs. Defaults to False.
|
|
127
|
+
- include_nested (bool, optional):
|
|
128
|
+
If True, also includes namespaces declared in nested elements.
|
|
129
|
+
Defaults to False.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
|
|
133
|
+
- set[str] | dict[Optional[str], str]:
|
|
134
|
+
A set of namespace URIs or a dict mapping prefixes to URIs.
|
|
135
|
+
The default namespace (no prefix) is represented as `None`.
|
|
136
|
+
|
|
137
|
+
Raises:
|
|
138
|
+
|
|
139
|
+
- Exception:
|
|
140
|
+
Any parsing or extraction error is propagated upstream for
|
|
141
|
+
centralized error handling.
|
|
142
|
+
|
|
143
|
+
Notes:
|
|
144
|
+
|
|
145
|
+
- If no namespaces are found, an empty set or dict is returned.
|
|
146
|
+
- The internal helper `_extract_nested_namespaces` performs a
|
|
147
|
+
recursive search of the XML tree when `include_nested` is
|
|
148
|
+
enabled.
|
|
149
|
+
- This method does not catch or handle errors — they are
|
|
150
|
+
reported as part of the validation error pipeline.
|
|
151
|
+
|
|
152
|
+
Example Usage:
|
|
153
|
+
|
|
154
|
+
>>> xml_root = etree.fromstring('<root xmlns:ns1=\"http://example.com/ns1\"/>')
|
|
155
|
+
>>> extract_xml_namespaces(xml_root)
|
|
156
|
+
{'http://example.com/ns1'}
|
|
157
|
+
|
|
158
|
+
>>> extract_xml_namespaces(xml_root, return_dict=True)
|
|
159
|
+
{'ns1': 'http://example.com/ns1'}
|
|
160
|
+
"""
|
|
161
|
+
def _extract_nested_namespaces(element: etree.ElementBase) -> dict[str | None, str]:
|
|
162
|
+
"""
|
|
163
|
+
Recursively extracts namespaces from all elements in the XML
|
|
164
|
+
tree.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
- element (etree.ElementBase):
|
|
168
|
+
The starting element for extraction.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
- dict[str | None, str]:
|
|
172
|
+
A dictionary mapping prefixes to URIs.
|
|
173
|
+
"""
|
|
174
|
+
all_namespaces = {}
|
|
175
|
+
for el in element.iter(None): # Explicitly passing `None` to avoid warnings.
|
|
176
|
+
# Merge any new namespaces found.
|
|
177
|
+
all_namespaces.update(
|
|
178
|
+
{
|
|
179
|
+
k.replace("xmlns:", "") if k else None: v
|
|
180
|
+
for k, v in ( el.nsmap or {} ).items()
|
|
181
|
+
}
|
|
182
|
+
)
|
|
183
|
+
return all_namespaces
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
# Collect all namespaces, including root.
|
|
187
|
+
if include_nested:
|
|
188
|
+
namespaces = _extract_nested_namespaces(xml_root)
|
|
189
|
+
# Extract namespaces explicitly from the root element.
|
|
190
|
+
else:
|
|
191
|
+
namespaces = {
|
|
192
|
+
k.replace("xmlns:", "") if k else None: v
|
|
193
|
+
# lxml provides namespaces through the nsmap attribute.
|
|
194
|
+
for k, v in ( xml_root.nsmap or {} ).items() # Ensure nsmap is a dictionary.
|
|
195
|
+
}
|
|
196
|
+
# Determine the return type based on `return_dict`.
|
|
197
|
+
return namespaces if return_dict else set(namespaces.values())
|
|
198
|
+
# Catch any exception, propagating it upstream for further handling.
|
|
199
|
+
except Exception: # pylint: disable=W0706:try-except-raise
|
|
200
|
+
# Keep the original error intact.
|
|
201
|
+
raise
|
|
202
|
+
|
|
203
|
+
@staticmethod
|
|
204
|
+
def get_file_paths(
|
|
205
|
+
file_path: str | Path,
|
|
206
|
+
file_type: str
|
|
207
|
+
) -> Tuple[List[Path], bool]:
|
|
208
|
+
"""
|
|
209
|
+
Resolves files from a given path and filters them by type.
|
|
210
|
+
|
|
211
|
+
If the path is a file, it returns a single-item list and a
|
|
212
|
+
True flag. If the path is a directory, it returns all files
|
|
213
|
+
with the matching extension and a boolean indicating whether
|
|
214
|
+
exactly one file was found.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
|
|
218
|
+
- file_path (str or Path):
|
|
219
|
+
Path to a file or directory to validate and inspect.
|
|
220
|
+
|
|
221
|
+
- file_type (str):
|
|
222
|
+
Expected file extension (e.g., "xml" or "xsd"). Used when
|
|
223
|
+
scanning a folder.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
|
|
227
|
+
- Tuple[List[Path], bool]:
|
|
228
|
+
- A list of resolved `Path` objects that match the file type.
|
|
229
|
+
- A boolean indicating whether exactly one file was found.
|
|
230
|
+
|
|
231
|
+
Raises:
|
|
232
|
+
|
|
233
|
+
- ValueError:
|
|
234
|
+
- If the path is neither a file nor a folder.
|
|
235
|
+
- If no files with the expected extension are found in a folder.
|
|
236
|
+
|
|
237
|
+
Notes:
|
|
238
|
+
|
|
239
|
+
- The method uses `.glob(f"*.{file_type}")` when inspecting folders.
|
|
240
|
+
- Paths are normalized using `_resolve_path()`.
|
|
241
|
+
"""
|
|
242
|
+
# Delegate resolving the provided path.
|
|
243
|
+
resolved_path = ValidatorUtils._resolve_path(file_path)
|
|
244
|
+
# Path is to a single file.
|
|
245
|
+
if resolved_path.is_file():
|
|
246
|
+
# Then return the file.
|
|
247
|
+
return [resolved_path], True
|
|
248
|
+
# Path is to a folder, assumed to hold one or more xsd files.
|
|
249
|
+
if resolved_path.is_dir():
|
|
250
|
+
# Get and resolve the path(s) to the file(s).
|
|
251
|
+
resolved_paths = list(
|
|
252
|
+
resolved_path.glob(f"*.{file_type}")
|
|
253
|
+
)
|
|
254
|
+
# Fail if there are no files in the folder.
|
|
255
|
+
if not resolved_paths:
|
|
256
|
+
raise ValueError(
|
|
257
|
+
f"No files reside in the folder: {resolved_paths}."
|
|
258
|
+
)
|
|
259
|
+
# There are one or more files in the folder.
|
|
260
|
+
return resolved_paths, len(resolved_paths) == 1
|
|
261
|
+
# Fail if the path is neither a file nor a folder.
|
|
262
|
+
raise ValueError(
|
|
263
|
+
f'The provided path is neither a file nor a folder: {resolved_path}'
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
@staticmethod
|
|
267
|
+
def match_namespace_to_schema(
|
|
268
|
+
xsd_schema: XMLSchema,
|
|
269
|
+
xml_namespaces: set[str]
|
|
270
|
+
) -> bool:
|
|
271
|
+
"""
|
|
272
|
+
Matches an XSD schema to an XML document based on namespace
|
|
273
|
+
rules.
|
|
274
|
+
|
|
275
|
+
This method verifies whether a given XSD schema is applicable to
|
|
276
|
+
an XML document by checking for namespace compatibility. The
|
|
277
|
+
matching logic follows these rules:
|
|
278
|
+
|
|
279
|
+
1. If the XSD schema defines a `target_namespace`, it must be
|
|
280
|
+
present in the XML document's declared namespaces.
|
|
281
|
+
2. If no match is found via `target_namespace`, the method
|
|
282
|
+
checks whether any of the schema's declared namespaces
|
|
283
|
+
(`xsd_schema.namespaces.values()`) are present in the XML.
|
|
284
|
+
3. If neither check passes, the schema is considered not to
|
|
285
|
+
match.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
|
|
289
|
+
- xsd_schema (XMLSchema):
|
|
290
|
+
The compiled XSD schema object to test against.
|
|
291
|
+
|
|
292
|
+
- xml_namespaces (set[str]):
|
|
293
|
+
A set of namespace URIs declared in the XML document.
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
|
|
297
|
+
- bool:
|
|
298
|
+
`True` if the schema matches the XML document's namespaces;
|
|
299
|
+
`False` otherwise.
|
|
300
|
+
|
|
301
|
+
Raises:
|
|
302
|
+
|
|
303
|
+
- Exception:
|
|
304
|
+
Propagates any unexpected errors encountered during namespace
|
|
305
|
+
access or comparison.
|
|
306
|
+
|
|
307
|
+
Notes:
|
|
308
|
+
|
|
309
|
+
- This function does not validate the XML against the schema — it only
|
|
310
|
+
performs a namespace-level compatibility check.
|
|
311
|
+
- Used internally to assist with schema selection during multi-schema
|
|
312
|
+
validation.
|
|
313
|
+
"""
|
|
314
|
+
try:
|
|
315
|
+
# Primary check: if target namespace is explicitly present in XML.
|
|
316
|
+
if xsd_schema.target_namespace is not None:
|
|
317
|
+
if xsd_schema.target_namespace in xml_namespaces:
|
|
318
|
+
return True
|
|
319
|
+
# Check if any other defined namespaces match.
|
|
320
|
+
if any(
|
|
321
|
+
ns in xml_namespaces for ns in xsd_schema.namespaces.values()
|
|
322
|
+
):
|
|
323
|
+
return True
|
|
324
|
+
# No matching namespace found.
|
|
325
|
+
return False
|
|
326
|
+
except Exception: # pylint: disable=W0706:try-except-raise
|
|
327
|
+
raise
|
|
328
|
+
|
|
329
|
+
@staticmethod
|
|
330
|
+
def sanity_check_files( # pylint: disable=R0914:too-many-locals
|
|
331
|
+
file_paths: List[Path],
|
|
332
|
+
base_url: Optional[str] = None,
|
|
333
|
+
error_facets: Optional[List[str]] = None,
|
|
334
|
+
parse_files: Optional[bool] = False
|
|
335
|
+
) -> ValidatorResult:
|
|
336
|
+
"""
|
|
337
|
+
Performs sanity checks on XML or XSD files and returns a
|
|
338
|
+
ValidatorResult instance.
|
|
339
|
+
|
|
340
|
+
This method checks each file for basic validity, including:
|
|
341
|
+
|
|
342
|
+
- Existence
|
|
343
|
+
- Non-empty content
|
|
344
|
+
- Correct file extension (".xml" or ".xsd")
|
|
345
|
+
- Optional well-formedness and XSD parsing (`parse_files=True`)
|
|
346
|
+
|
|
347
|
+
If any file fails a check, its error details are collected and
|
|
348
|
+
returned as part of a `ValidatorResult` object. Otherwise, the
|
|
349
|
+
validation is marked as successful.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
|
|
353
|
+
- file_paths (List[Path]):
|
|
354
|
+
A list of file paths (XML or XSD) to validate.
|
|
355
|
+
|
|
356
|
+
- base_url (Optional[str]):
|
|
357
|
+
An optional base URL to resolve includes or imports during
|
|
358
|
+
parsing (used when `parse_files=True`).
|
|
359
|
+
|
|
360
|
+
- error_facets (Optional[List[str]]):
|
|
361
|
+
A list of exception attributes to extract and include in the
|
|
362
|
+
error details (e.g., "msg", "position").
|
|
363
|
+
|
|
364
|
+
- parse_files (bool, optional):
|
|
365
|
+
If True, performs well-formedness checks and XSD schema
|
|
366
|
+
validation. Defaults to False.
|
|
367
|
+
|
|
368
|
+
Returns:
|
|
369
|
+
|
|
370
|
+
- ValidatorResult:
|
|
371
|
+
An object with `success: bool` and `error: List[Dict[str, Any]]`.
|
|
372
|
+
|
|
373
|
+
Notes:
|
|
374
|
+
|
|
375
|
+
- This method catches `OSError`, `etree.XMLSyntaxError`,
|
|
376
|
+
`etree.ParseError`, and related schema exceptions.
|
|
377
|
+
- If no errors are found, `ValidatorResult.success` is True and
|
|
378
|
+
`error` is None.
|
|
379
|
+
- Used during initial file intake to catch structural issues before
|
|
380
|
+
full validation begins.
|
|
381
|
+
"""
|
|
382
|
+
# Explicitly type errors and default_facets for clarity.
|
|
383
|
+
errors: List[ dict[str, Optional[str]] ] = []
|
|
384
|
+
default_facets: dict[ type, list[str] ] = {
|
|
385
|
+
OSError: ["strerror"],
|
|
386
|
+
etree.ParseError: ["msg", "position"],
|
|
387
|
+
etree.XMLSchemaParseError: ["msg", "position"],
|
|
388
|
+
etree.XMLSyntaxError: ["msg", "position"]
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
# Helper function to process errors into a data structure.
|
|
392
|
+
def append_error(
|
|
393
|
+
file_path: Path,
|
|
394
|
+
reason: str,
|
|
395
|
+
additional_details: Optional[dict] = None
|
|
396
|
+
) -> None:
|
|
397
|
+
"""
|
|
398
|
+
Helper function to append an error dictionary to the errors
|
|
399
|
+
list.
|
|
400
|
+
"""
|
|
401
|
+
# Always add the file path and the general reason.
|
|
402
|
+
error: dict[ str, Optional[str] ] = {
|
|
403
|
+
"file": str(file_path),
|
|
404
|
+
"reason": reason
|
|
405
|
+
}
|
|
406
|
+
# Optionally, add more error details (if provided).
|
|
407
|
+
if additional_details:
|
|
408
|
+
error.update(additional_details)
|
|
409
|
+
# Add the error to the container list (of errors).
|
|
410
|
+
errors.append(error)
|
|
411
|
+
|
|
412
|
+
for file_path in file_paths:
|
|
413
|
+
# Establish the file type/extension for the current file.
|
|
414
|
+
file_type = file_path.suffix.lower()
|
|
415
|
+
# General validations.
|
|
416
|
+
if file_type not in {".xml", ".xsd"}:
|
|
417
|
+
# Incorrect file exstension.
|
|
418
|
+
append_error(
|
|
419
|
+
file_path,
|
|
420
|
+
f"Unsupported file type: {file_type}.",
|
|
421
|
+
{"Error type": "ValueError"}
|
|
422
|
+
)
|
|
423
|
+
continue
|
|
424
|
+
if not file_path.exists():
|
|
425
|
+
# File does not exist.
|
|
426
|
+
append_error(
|
|
427
|
+
file_path,
|
|
428
|
+
f"The {file_type.removeprefix('.')} file does not exist.",
|
|
429
|
+
{"Error type": "OSError"}
|
|
430
|
+
)
|
|
431
|
+
continue
|
|
432
|
+
if file_path.stat().st_size == 0:
|
|
433
|
+
# File is empty.
|
|
434
|
+
append_error(
|
|
435
|
+
file_path,
|
|
436
|
+
"File is empty.",
|
|
437
|
+
{"Error type": "ValueError"}
|
|
438
|
+
)
|
|
439
|
+
continue
|
|
440
|
+
# XML/XSD specific validations.
|
|
441
|
+
try:
|
|
442
|
+
# Read the file.
|
|
443
|
+
with file_path.open("rb") as file:
|
|
444
|
+
# Validate well-formedness (xml or xsd).
|
|
445
|
+
if parse_files:
|
|
446
|
+
tree = etree.parse(
|
|
447
|
+
file,
|
|
448
|
+
parser=etree.XMLParser(),
|
|
449
|
+
base_url=base_url # type:ignore
|
|
450
|
+
)
|
|
451
|
+
# Validate the XSD schema as such.
|
|
452
|
+
if file_type == '.xsd':
|
|
453
|
+
_ = etree.XMLSchema(tree)
|
|
454
|
+
# Handle validation failures.
|
|
455
|
+
except (
|
|
456
|
+
OSError,
|
|
457
|
+
etree.ParseError,
|
|
458
|
+
etree.XMLSchemaParseError,
|
|
459
|
+
etree.XMLSyntaxError
|
|
460
|
+
) as e:
|
|
461
|
+
# Determine error facets based on error type or provided facets.
|
|
462
|
+
facets_to_include = error_facets or default_facets.get(
|
|
463
|
+
type(e), []
|
|
464
|
+
)
|
|
465
|
+
# Initialize the error details dict.
|
|
466
|
+
error_details: dict[str, Optional[str]] = {}
|
|
467
|
+
# Add aspects/details of the caught error.
|
|
468
|
+
for facet in facets_to_include:
|
|
469
|
+
if hasattr(e, facet):
|
|
470
|
+
value = getattr(e, facet, None)
|
|
471
|
+
# Handle tuple values like (line, column).
|
|
472
|
+
if isinstance(value, tuple):
|
|
473
|
+
value = f"Line {value[0]}, Column {value[1]}."
|
|
474
|
+
error_details[facet] = value
|
|
475
|
+
logger.warn(
|
|
476
|
+
f"Facet '{facet}' is not an attribute of error type'{type(e).__name__}'."
|
|
477
|
+
)
|
|
478
|
+
# Add the error type to the error details
|
|
479
|
+
error_details['Error type'] = type(e).__name__
|
|
480
|
+
# Append the error to the return list.
|
|
481
|
+
append_error(file_path, "File parsing failed.", error_details)
|
|
482
|
+
# Determine success based on whether there are errors.
|
|
483
|
+
success = len(errors) == 0
|
|
484
|
+
return ValidatorResult(success=success, error=errors)
|