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,1605 @@
|
|
|
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
|
+
This module defines the `XmlValidator` class — a Robot Framework test
|
|
20
|
+
library for validating XML files against XSD schemas using the
|
|
21
|
+
`xmlschema` library.
|
|
22
|
+
|
|
23
|
+
The validator supports both individual and batch validation workflows,
|
|
24
|
+
with comprehensive error reporting and optional export to structured
|
|
25
|
+
CSV files.
|
|
26
|
+
|
|
27
|
+
Key Features:
|
|
28
|
+
|
|
29
|
+
- Validation of a single XML file against a specified schema.
|
|
30
|
+
- Batch validation of multiple XML files in a folder, using:
|
|
31
|
+
- A single shared schema.
|
|
32
|
+
- Dynamic schema matching by namespace or file name.
|
|
33
|
+
- Detailed error reporting for validation failures.
|
|
34
|
+
- Namespace-aware validation for strict conformance.
|
|
35
|
+
- Graceful handling of malformed XML or XSD files.
|
|
36
|
+
- Optional export of all collected errors to a CSV file.
|
|
37
|
+
|
|
38
|
+
This module is intended to be imported by Robot Framework test suites
|
|
39
|
+
or executed as a Python module via a direct call.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# pylint: disable=C0103:invalid-name # On account of the module name, that is not snake-cased (required by Robot Framework).
|
|
44
|
+
# pylint: disable=C0302:too-many-lines # On account of the extensive docstrings and annotations.
|
|
45
|
+
# pylint: disable=C0301:line-too-long # On account of tables in docstrings.
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# Standard library imports.
|
|
49
|
+
from pathlib import Path
|
|
50
|
+
from typing import Any, Dict, List, Literal, Optional, Tuple
|
|
51
|
+
# Third party library imports.
|
|
52
|
+
from lxml import etree
|
|
53
|
+
from robot.api import Failure, logger
|
|
54
|
+
from robot.api.deco import keyword, library
|
|
55
|
+
from xmlschema import XMLSchema
|
|
56
|
+
# Local application imports.
|
|
57
|
+
from .xml_validator_results import ValidatorResultRecorder, ValidatorResult
|
|
58
|
+
from .xml_validator_utils import ValidatorUtils
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@library(scope='GLOBAL')
|
|
62
|
+
class XmlValidator:
|
|
63
|
+
"""
|
|
64
|
+
XmlValidator is a `Robot Framework <https://robotframework.org/>`_
|
|
65
|
+
test library for validating XML files against XSD schemas.
|
|
66
|
+
|
|
67
|
+
The library leverages the power of the
|
|
68
|
+
`xmlschema library <https://pypi.org/project/xmlschema/>`_ and is
|
|
69
|
+
designed for both single-file and batch XML validation workflows.
|
|
70
|
+
|
|
71
|
+
It provides structured and detailed reporting of XML parse errors
|
|
72
|
+
(malformed XML content) and XSD violations, schema auto-detection
|
|
73
|
+
and CSV exports of collected errors.
|
|
74
|
+
|
|
75
|
+
Features are described in detail on the `project repo's landing page.
|
|
76
|
+
<https://github.com/MichaelHallik/robotframework-xmlvalidator>`_.
|
|
77
|
+
|
|
78
|
+
**Overview**
|
|
79
|
+
|
|
80
|
+
The main keyword is ``Validate Xml Files``.
|
|
81
|
+
|
|
82
|
+
The other keywords are convenience/helper functions, e.g. ``Reset
|
|
83
|
+
Error Facets``.
|
|
84
|
+
|
|
85
|
+
The ``Validate Xml Files`` validates one or more XML files against one
|
|
86
|
+
or more XSD schema files and collects and reports all encountered
|
|
87
|
+
errors.
|
|
88
|
+
|
|
89
|
+
The type of error that the keyword can detect is not limited to XSD
|
|
90
|
+
violations, but may also pertain to malformed XML files (e.g. parse
|
|
91
|
+
errors), empty files, unmatched XML files (no XSD match found) and
|
|
92
|
+
others.
|
|
93
|
+
|
|
94
|
+
Errors that result from malformed XML files or from XSD violations
|
|
95
|
+
support detailed error reporting. Using the ``error_facets`` argument
|
|
96
|
+
you may specify the details the keyword should collect and report
|
|
97
|
+
about captured errors.
|
|
98
|
+
|
|
99
|
+
When operating in batch mode, the ``Validate Xml Files`` keyword
|
|
100
|
+
always validates the entire set of passed XML files. That is, when
|
|
101
|
+
it encounters an error in a file, it does not simply fail. Rather,
|
|
102
|
+
it collects the error details (as determined by the error_facets
|
|
103
|
+
arg) and then continues validating the current file as well as any
|
|
104
|
+
subsequent file(s).
|
|
105
|
+
|
|
106
|
+
In that fashion the keyword works through the entire set of files.
|
|
107
|
+
|
|
108
|
+
When having finished checking the last file, it will log a summary
|
|
109
|
+
of the test run and then proceed to report all collected errors in
|
|
110
|
+
the console, in the RF log and, optionally, in the form of a CSV
|
|
111
|
+
file.
|
|
112
|
+
|
|
113
|
+
**Batch mode & dynamic XSD matching**
|
|
114
|
+
|
|
115
|
+
The ``Validate Xml Files`` keyword supports validating a single,
|
|
116
|
+
individual XML file against an XSD schema. It also supports batch
|
|
117
|
+
flows, by being able to validate multiple XML files in a specified
|
|
118
|
+
folder against one or more XSD schema files. These XSD files may
|
|
119
|
+
either reside in the same folder (as the XML files) or in a
|
|
120
|
+
different folder.
|
|
121
|
+
|
|
122
|
+
In the latter case (i.e. when multiple schema files are
|
|
123
|
+
involved) XML files are matched dynamically to XSD files, supporting
|
|
124
|
+
either a 'by filename' strategy or a 'by namespace strategy'.
|
|
125
|
+
|
|
126
|
+
That means you can simply pass the paths to a folder containing
|
|
127
|
+
XML files and to a folder containing XSD files and the library
|
|
128
|
+
will determine which XSD schema file to use for each XML file.
|
|
129
|
+
|
|
130
|
+
If the XML and XSD files reside in the same folder, you only
|
|
131
|
+
have to pass one folder path and the library will dynamically pair
|
|
132
|
+
each XML file with the relevant XSD schema file.
|
|
133
|
+
|
|
134
|
+
When no matching XSD schema could be identified for an XML file,
|
|
135
|
+
this will be integrated into the error reporting (the keyword will
|
|
136
|
+
not fail).
|
|
137
|
+
|
|
138
|
+
As mentioned earlier, you may also refer to specific XML/XSD files
|
|
139
|
+
(instead of to folders). In that case, no matching will be
|
|
140
|
+
attempted, but the library will simply try to validate the specified
|
|
141
|
+
XML file against the specified XSD file.
|
|
142
|
+
|
|
143
|
+
**Schema pre-loading**
|
|
144
|
+
|
|
145
|
+
The library supports loading a schema by specifying an ``xsd_path``
|
|
146
|
+
when importing the library in a Robot Framework test suite. The
|
|
147
|
+
preloaded schema is then reused for all validations until
|
|
148
|
+
overridden at the test case level.
|
|
149
|
+
|
|
150
|
+
**Comprehensive, robust and flexible error reporting**
|
|
151
|
+
|
|
152
|
+
- Captures XSD schema violations.
|
|
153
|
+
|
|
154
|
+
- Missing required elements.
|
|
155
|
+
- Cardinality constraints.
|
|
156
|
+
- Datatype mismatches (e.g., invalid `xs:dateTime`).
|
|
157
|
+
- Pattern and enumeration violations.
|
|
158
|
+
- Namespace errors.
|
|
159
|
+
- Etc.
|
|
160
|
+
|
|
161
|
+
- Captures malformed XML.
|
|
162
|
+
- Handles edge cases like empty files or XML files that could not be
|
|
163
|
+
matched to an XSD schema file.
|
|
164
|
+
- Does not fail on errors, but collects encountered errors in all
|
|
165
|
+
files and reports them in a structured format in the console and
|
|
166
|
+
RF log.
|
|
167
|
+
- Supports specifying the details that should be collected for
|
|
168
|
+
encountered errors.
|
|
169
|
+
- Optionally exports the error report to a CSV file, providing the
|
|
170
|
+
file name next to all error details for traceability.
|
|
171
|
+
|
|
172
|
+
**Customizing error collection**
|
|
173
|
+
|
|
174
|
+
Use the ``error_facets`` argument to control which attributes of
|
|
175
|
+
detected errors will be collected and reported. E.g. the element
|
|
176
|
+
locator (XPath), error message, involved namespace and/or the XSD
|
|
177
|
+
validator that failed.
|
|
178
|
+
|
|
179
|
+
Error facets can be set by passing a list of one or more error
|
|
180
|
+
facets, either with the library import and/or on the test case
|
|
181
|
+
level (i.e. when calling the ``Validate XML Files`` keyword).
|
|
182
|
+
|
|
183
|
+
These are the facets (or attributes) that can be collected and
|
|
184
|
+
reported for each encountered error:
|
|
185
|
+
|
|
186
|
+
+---------------+----------------------------------------------------------------------------+
|
|
187
|
+
| Facet | Description |
|
|
188
|
+
+===============+============================================================================+
|
|
189
|
+
| ``message`` | A human-readable message describing the validation error. |
|
|
190
|
+
+---------------+----------------------------------------------------------------------------+
|
|
191
|
+
| ``path`` | The XPath location of the error in the XML document. |
|
|
192
|
+
+---------------+----------------------------------------------------------------------------+
|
|
193
|
+
| ``domain`` | The domain of the error (e.g., "validation"). |
|
|
194
|
+
+---------------+----------------------------------------------------------------------------+
|
|
195
|
+
| ``reason`` | The reason for the error, often linked to XSD constraint violations. |
|
|
196
|
+
+---------------+----------------------------------------------------------------------------+
|
|
197
|
+
| ``validator`` | The XSD component (e.g., element, attribute, type) that failed validation. |
|
|
198
|
+
+---------------+----------------------------------------------------------------------------+
|
|
199
|
+
| ``schema_path`` | The XPath location of the error in the XSD schema. |
|
|
200
|
+
+---------------+----------------------------------------------------------------------------+
|
|
201
|
+
| ``namespaces`` | The namespaces involved in the error (if applicable). |
|
|
202
|
+
+---------------+----------------------------------------------------------------------------+
|
|
203
|
+
| ``elem`` | The XML element that caused the error (``ElementTree.Element``). |
|
|
204
|
+
+---------------+----------------------------------------------------------------------------+
|
|
205
|
+
| ``value`` | The invalid value that triggered the error. |
|
|
206
|
+
+---------------+----------------------------------------------------------------------------+
|
|
207
|
+
| ``severity`` | The severity level of the error (not always present). |
|
|
208
|
+
+---------------+----------------------------------------------------------------------------+
|
|
209
|
+
| ``args`` | The arguments passed to the error message formatting. |
|
|
210
|
+
+---------------+----------------------------------------------------------------------------+
|
|
211
|
+
|
|
212
|
+
For each error that is encountered, the selected error facet(s) will
|
|
213
|
+
be collected and reported.
|
|
214
|
+
|
|
215
|
+
Error facets passed during library initialization will be overruled
|
|
216
|
+
by error facets that are passed at the test case level, when calling
|
|
217
|
+
the ``Validate Xml Files`` keyword.
|
|
218
|
+
|
|
219
|
+
The values you can pass through the `error_facets` argument are
|
|
220
|
+
based on the attributes of the error objects as returned by the
|
|
221
|
+
XMLSchema.iter_errors() method, that is provided by the xmlschema
|
|
222
|
+
library and the the xmlvalidator library leverages. Said method
|
|
223
|
+
yields instances of
|
|
224
|
+
xmlschema.validators.exceptions.XMLSchemaValidationError (or its
|
|
225
|
+
subclasses), each representing a specific validation issue
|
|
226
|
+
encountered in an XML file. These error objects expose various
|
|
227
|
+
attributes that describe the nature, location, and cause of the
|
|
228
|
+
problem.
|
|
229
|
+
|
|
230
|
+
The table lists the most commonly available attributes, though
|
|
231
|
+
additional fields may be available depending on the type of
|
|
232
|
+
validation error.
|
|
233
|
+
|
|
234
|
+
**Support for XSD includes/imports**
|
|
235
|
+
|
|
236
|
+
Enables resolution of schema imports/includes via a custom base URL,
|
|
237
|
+
via the ``base_url`` arg.
|
|
238
|
+
|
|
239
|
+
Use ``base_url`` when your XSD uses <xs:include> or <xs:import> with
|
|
240
|
+
relative paths.
|
|
241
|
+
|
|
242
|
+
You can pass ``base_url`` with the library import (together with
|
|
243
|
+
passing ``xsd_path``) and/or when calling ``Validate Xml Files``
|
|
244
|
+
with ``xsd_path``.
|
|
245
|
+
|
|
246
|
+
** Optional test case fail **
|
|
247
|
+
|
|
248
|
+
The ``Validate Xml Files`` keyword collects one or more errors for
|
|
249
|
+
one or more XML files. As mentioned earlier, the keyword is designed
|
|
250
|
+
so as to not fail upon encountering errors.
|
|
251
|
+
|
|
252
|
+
However, in case you want your test case to fail when one or more
|
|
253
|
+
errors have been detected, you can use the ``fail_on_errors`` (bool)
|
|
254
|
+
argument to make it so. It defaults to ${False}.
|
|
255
|
+
|
|
256
|
+
**Basic usage examples**
|
|
257
|
+
|
|
258
|
+
For a comprehensive set of example test cases, please see the
|
|
259
|
+
`Robot Framework integration tests <https://github.com/MichaelHallik/robotframework-xmlvalidator/tree/main/test/integration/>`_
|
|
260
|
+
in the projects GitHub repository.
|
|
261
|
+
|
|
262
|
+
The repo contains a
|
|
263
|
+
`structured overview of all implemented tests <https://github.com/MichaelHallik/robotframework-xmlvalidator/blob/main/test/_doc/integration/overview.html>`_
|
|
264
|
+
per topic (e.g. library import, schema matching strategies, etc.).
|
|
265
|
+
|
|
266
|
+
It further contains a detailed instruction on
|
|
267
|
+
`how to run Robot Framework tests <https://github.com/MichaelHallik/robotframework-xmlvalidator/blob/main/test/_doc/integration/README.md>`_.
|
|
268
|
+
|
|
269
|
+
.. code:: robotframework
|
|
270
|
+
|
|
271
|
+
*** Settings ***
|
|
272
|
+
Library XmlValidator xsd_path=path/to/default/schema.xsd
|
|
273
|
+
|
|
274
|
+
*** Variables ***
|
|
275
|
+
${SINGLE_XML_FILE} path/to/file1.xml
|
|
276
|
+
${FOLDER_MULTIPLE_XML} path/to/xml_folder_1
|
|
277
|
+
${FOLDER_MULTIPLE_XML_ALT} path/to/xml_folder_2
|
|
278
|
+
${FOLDER_MULTIPLE_XML_NS} path/to/xml_folder_3
|
|
279
|
+
${FOLDER_MULTIPLE_XML_FN} path/to/xml_folder_4
|
|
280
|
+
|
|
281
|
+
${SINGLE_XSD_FILE} path/to/alt_schema.xsd
|
|
282
|
+
${FOLDER_MULTIPLE_XSD} path/to/xsd_schemas/
|
|
283
|
+
|
|
284
|
+
*** Test Cases ***
|
|
285
|
+
|
|
286
|
+
Validate Single XML File With Default Schema
|
|
287
|
+
[Documentation] Validates a single XML file using the default schema
|
|
288
|
+
Validate Xml Files ${SINGLE_XML_FILE}
|
|
289
|
+
|
|
290
|
+
Validate Folder Of XML Files With Default Schema
|
|
291
|
+
[Documentation] Validates all XML files in a folder using the default schema
|
|
292
|
+
Validate Xml Files ${FOLDER_MULTIPLE_XML}
|
|
293
|
+
|
|
294
|
+
Validate Folder With Explicit Schema Override
|
|
295
|
+
[Documentation] Validates XML files using a different, explicitly provided schema
|
|
296
|
+
Validate Xml Files ${FOLDER_MULTIPLE_XML_ALT} ${SINGLE_XSD_FILE}
|
|
297
|
+
|
|
298
|
+
Validate Folder With Multiple Schemas By Namespace
|
|
299
|
+
[Documentation] Resolves matching schema for each XML file based on namespace
|
|
300
|
+
Validate Xml Files ${FOLDER_MULTIPLE_XML_NS} ${FOLDER_MULTIPLE_XSD} xsd_search_strategy=by_namespace
|
|
301
|
+
|
|
302
|
+
Validate Folder With Multiple Schemas By File Name
|
|
303
|
+
[Documentation] Resolves schema based on matching file name patterns (no schema path passed)
|
|
304
|
+
Validate Xml Files ${FOLDER_MULTIPLE_XML_FN} xsd_search_strategy=by_file_name
|
|
305
|
+
|
|
306
|
+
Example of the console output where some files passed validation and
|
|
307
|
+
multiple errors have been found for multiple other files:
|
|
308
|
+
|
|
309
|
+
.. code:: console
|
|
310
|
+
|
|
311
|
+
Schema 'schema.xsd' set.
|
|
312
|
+
Collecting error facets: ['path', 'reason'].
|
|
313
|
+
XML Validator ready for use!
|
|
314
|
+
==============================================================================
|
|
315
|
+
01 Advanced Validation:: Demo XML validation
|
|
316
|
+
Mapping XML files to schemata by namespace.
|
|
317
|
+
Validating 'valid_1.xml'.
|
|
318
|
+
XML is valid!
|
|
319
|
+
Validating 'valid_2.xml'.
|
|
320
|
+
XML is valid!
|
|
321
|
+
Validating 'valid_3.xml'.
|
|
322
|
+
XML is valid!
|
|
323
|
+
Validating 'xsd_violations_1.xml'.
|
|
324
|
+
Setting new schema file: C:\\Projects\\robotframework-xmlvalidator\\test\\_data\\integration\\TC_01\\schema1.xsd.
|
|
325
|
+
[ WARN ] XML is invalid:
|
|
326
|
+
[ WARN ] Error #0:
|
|
327
|
+
[ WARN ] path: /Employee
|
|
328
|
+
[ WARN ] reason: Unexpected child with tag '{http://example.com/schema1}FullName' at position 2. Tag '{http://example.com/schema1}Name' expected.
|
|
329
|
+
[ WARN ] Error #1:
|
|
330
|
+
[ WARN ] path: /Employee/Age
|
|
331
|
+
[ WARN ] reason: invalid literal for int() with base 10: 'Twenty Five'
|
|
332
|
+
[ WARN ] Error #2:
|
|
333
|
+
[ WARN ] path: /Employee/ID
|
|
334
|
+
[ WARN ] reason: invalid literal for int() with base 10: 'ABC'
|
|
335
|
+
Validating 'valid_.xml_4'.
|
|
336
|
+
XML is valid!
|
|
337
|
+
Validating 'valid_.xml_5'.
|
|
338
|
+
XML is valid!
|
|
339
|
+
Validating 'malformed_xml_1.xml'.
|
|
340
|
+
[ WARN ] XML is invalid:
|
|
341
|
+
[ WARN ] Error #0:
|
|
342
|
+
[ WARN ] reason: Premature end of data in tag Name line 1, line 1, column 37 (file:/C:/Projects/robotframework-xmlvalidator/test/_data/integration/TC_01/malformed_xml_1.xml, line 1)
|
|
343
|
+
[ WARN ] Error #1:
|
|
344
|
+
[ WARN ] reason: Opening and ending tag mismatch: ProductID line 1 and Product, line 1, column 31 (file:/C:/Projects/robotframework-xmlvalidator/test/_data/integration/TC_01/malformed_xml_1.xml, line 1)
|
|
345
|
+
Validating 'xsd_violations_2.xml'.
|
|
346
|
+
Setting new schema file: C:\\Projects\\robotframework-xmlvalidator\\test\\_data\\integration\\TC_01\\schema2.xsd.
|
|
347
|
+
[ WARN ] XML is invalid:
|
|
348
|
+
[ WARN ] Error #0:
|
|
349
|
+
[ WARN ] path: /Product/Price
|
|
350
|
+
[ WARN ] reason: invalid value '99.99USD' for xs:decimal
|
|
351
|
+
[ WARN ] Error #1:
|
|
352
|
+
[ WARN ] path: /Product
|
|
353
|
+
[ WARN ] reason: The content of element '{http://example.com/schema2}Product' is not complete. Tag '{http://example.com/schema2}Price' expected.
|
|
354
|
+
Validating 'valid_.xml_6'.
|
|
355
|
+
XML is valid!
|
|
356
|
+
Validating 'no_xsd_match_1.xml'.
|
|
357
|
+
[ WARN ] XML is invalid:
|
|
358
|
+
[ WARN ] Error #0:
|
|
359
|
+
[ WARN ] reason: No matching XSD found for: no_xsd_match_1.xml.
|
|
360
|
+
Validating 'no_xsd_match_2.xml'.
|
|
361
|
+
[ WARN ] XML is invalid:
|
|
362
|
+
[ WARN ] Error #0:
|
|
363
|
+
[ WARN ] reason: No matching XSD found for: no_xsd_match_2.xml.
|
|
364
|
+
Validation errors exported to 'C:\\test\\01_Advanced_Validation\\errors_2025-03-29_13-54-46-552150.csv'.
|
|
365
|
+
Total_files validated: 11.
|
|
366
|
+
Valid files: 6.
|
|
367
|
+
Invalid files: 5
|
|
368
|
+
01 Advanced Validation:: Demo XML validation | PASS |
|
|
369
|
+
21 errors have been detected.
|
|
370
|
+
========================================================
|
|
371
|
+
01 Advanced Validation:: Demo XML validation | PASS |
|
|
372
|
+
1 test, 1 passed, 0 failed
|
|
373
|
+
|
|
374
|
+
In case ``fail_on_errors`` is True, the console output will look like this:
|
|
375
|
+
|
|
376
|
+
.. code:: console
|
|
377
|
+
|
|
378
|
+
Validation errors exported to 'C:\\test\\01_Advanced_Validation\\errors_2025-03-29_13-54-46-552150.csv'.
|
|
379
|
+
Total_files validated: 11.
|
|
380
|
+
Valid files: 6.
|
|
381
|
+
Invalid files: 5.
|
|
382
|
+
01 Advanced Validation:: Demo XML validation | FAIL |
|
|
383
|
+
21 errors have been detected.
|
|
384
|
+
========================================================
|
|
385
|
+
01 Advanced Validation:: Demo XML validation | FAIL |
|
|
386
|
+
1 test, 0 passed, 1 failed
|
|
387
|
+
|
|
388
|
+
The corresponding CSV output would in both cases look like:
|
|
389
|
+
|
|
390
|
+
.. code:: text
|
|
391
|
+
|
|
392
|
+
file_name,path,reason
|
|
393
|
+
xsd_violations_1.xml,/Employee/ID,invalid literal for int() with base 10: 'ABC'
|
|
394
|
+
xsd_violations_1.xml,/Employee/Age,invalid literal for int() with base 10: 'Twenty Five'
|
|
395
|
+
xsd_violations_1.xml,/Employee,Unexpected child with tag '{http://example.com/schema1}FullName' at position 2. Tag '{http://example.com/schema1}Name' expected.
|
|
396
|
+
malformed_xml_1.xml,,"Premature end of data in tag Name line 1, line 1, column 37 (file:/C:/Projects/robotframework-xmlvalidator/test/_data/integration/TC_01/schema1_malformed_2.xml, line 1)"
|
|
397
|
+
malformed_xml_1.xml,,"Opening and ending tag mismatch: ProductID line 1 and Product, line 1, column 31 (file:/C:/Projects/robotframework-xmlvalidator/test/_data/integration/TC_01/schema2_malformed_3.xml, line 1)"
|
|
398
|
+
schema2_invalid_1.xml,/Product/Price,invalid value '99.99USD' for xs:decimal
|
|
399
|
+
schema2_invalid_2.xml,/Product,The content of element '{http://example.com/schema2}Product' is not complete. Tag '{http://example.com/schema2}Price' expected.
|
|
400
|
+
no_xsd_match_1.xml,,No matching XSD found for: no_xsd_match_1.xml.
|
|
401
|
+
no_xsd_match_2.xml,,No matching XSD found for: no_xsd_match_2.xml.
|
|
402
|
+
|
|
403
|
+
"""
|
|
404
|
+
|
|
405
|
+
__version__ = '1.0.0'
|
|
406
|
+
ROBOT_LIBRARY_DOC_FORMAT = 'reST'
|
|
407
|
+
nr_instances = 0
|
|
408
|
+
|
|
409
|
+
def __init__(
|
|
410
|
+
self,
|
|
411
|
+
xsd_path: Optional[str | Path] = None,
|
|
412
|
+
base_url: Optional[str] = None,
|
|
413
|
+
error_facets: Optional[ List[str] ] = None
|
|
414
|
+
) -> None:
|
|
415
|
+
"""
|
|
416
|
+
**Library Scope**
|
|
417
|
+
|
|
418
|
+
The XmlValidator library has a ``GLOBAL``
|
|
419
|
+
`library scope <https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#library-scope>`_
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
**Library Arguments**
|
|
423
|
+
|
|
424
|
+
+--------------+--------------+-----------+---------------------------------------------------------------------------------------------+
|
|
425
|
+
| Argument | Type | Required | Description |
|
|
426
|
+
+==============+==============+===========+=============================================================================================+
|
|
427
|
+
| xsd_path | str | No | Path to an XSD file or folder to preload during initialization. |
|
|
428
|
+
| | | | In case of a folder, the folder must hold one file only. |
|
|
429
|
+
+--------------+--------------+-----------+---------------------------------------------------------------------------------------------+
|
|
430
|
+
| base_url | str | No | Base path used to resolve includes/imports within the provided XSD schema. |
|
|
431
|
+
+--------------+--------------+-----------+---------------------------------------------------------------------------------------------+
|
|
432
|
+
| error_facets | list of str | No | The attributes of validation errors to collect and report. E.g. ``path``, ``reason``. |
|
|
433
|
+
+--------------+--------------+-----------+---------------------------------------------------------------------------------------------+
|
|
434
|
+
|
|
435
|
+
All arguments are optional.
|
|
436
|
+
|
|
437
|
+
``xsd_path``
|
|
438
|
+
|
|
439
|
+
Must be a (valid) path to a single XSD file. If the path
|
|
440
|
+
points to a directory, to a file without '.xsd' extension or
|
|
441
|
+
to an invalid or corrupt XSD file, an appropriate exception
|
|
442
|
+
will be raised.
|
|
443
|
+
|
|
444
|
+
The optional ``xsd_path`` parameter allows pre-loading a
|
|
445
|
+
specific XSD schema file during initialization. This schema
|
|
446
|
+
will then be used as the schema for all subsequent calls to
|
|
447
|
+
``Validate Xml Files`` that do not themselves pass a path to
|
|
448
|
+
an XSD schema file or to a folder containing one or more XSD
|
|
449
|
+
files.
|
|
450
|
+
|
|
451
|
+
As soon as an ``xsd_path`` (to a schema file or a folder
|
|
452
|
+
holding one or more schema files) is passed to
|
|
453
|
+
``Validate Xml Files``, at the test case level, a schema
|
|
454
|
+
file that was loaded during library initialization will be
|
|
455
|
+
overriden.
|
|
456
|
+
|
|
457
|
+
If no schema has been set during initialization, then a path
|
|
458
|
+
to an XSD schema file (or a folder holding one or more
|
|
459
|
+
schema files) must be supplied in the very first call to
|
|
460
|
+
``Validate Xml Files`` to prevent the call from failing.
|
|
461
|
+
|
|
462
|
+
If no schema has been set during library import or by a
|
|
463
|
+
precending call to ``Validate Xml Files``, then any call to
|
|
464
|
+
the keyword that does not provide a schema, will fail.
|
|
465
|
+
|
|
466
|
+
Each time a schema file is passed and set, all subsequent calls
|
|
467
|
+
to ``Validate Xml Files`` will use that schema, unless it is
|
|
468
|
+
replaced by passing a new ``xsd_path`` with a call.
|
|
469
|
+
|
|
470
|
+
``base_url``
|
|
471
|
+
|
|
472
|
+
The `base_url` parameter is used to resolve relative imports
|
|
473
|
+
and includes within the XSD schema. It should point to the
|
|
474
|
+
base directory or URL for resolving relative paths in the
|
|
475
|
+
XSD schema, such as imports and includes.
|
|
476
|
+
|
|
477
|
+
``error_facets``
|
|
478
|
+
|
|
479
|
+
This parameter accepts a list of error attributes to collect
|
|
480
|
+
during validation failures. If not provided, it will be set to
|
|
481
|
+
the default: ['path', 'reason'].
|
|
482
|
+
|
|
483
|
+
Using ``error_facets`` you can control which attributes of
|
|
484
|
+
validation errors are to be collected and, ultimately, reported.
|
|
485
|
+
|
|
486
|
+
See the introduction for more details on the purpose and usage
|
|
487
|
+
of error facets.
|
|
488
|
+
|
|
489
|
+
**Examples**
|
|
490
|
+
|
|
491
|
+
Using a preloaded schema:
|
|
492
|
+
|
|
493
|
+
.. code:: robotframework
|
|
494
|
+
|
|
495
|
+
************* Settings ***
|
|
496
|
+
Library xmlvalidator xsd_path=path/to/schema.xsd
|
|
497
|
+
|
|
498
|
+
Defer schema loading to the test case(s):
|
|
499
|
+
|
|
500
|
+
.. code:: robotframework
|
|
501
|
+
|
|
502
|
+
**********Library xmlvalidator
|
|
503
|
+
|
|
504
|
+
Importing with preloaded XSD that requires a base_url:
|
|
505
|
+
|
|
506
|
+
.. code:: robotframework
|
|
507
|
+
|
|
508
|
+
**********Library xmlvalidator xsd_path=path/to/schema_with_include.xsd
|
|
509
|
+
**********... base_url=path/to/include_schemas
|
|
510
|
+
|
|
511
|
+
Use ``base_url`` when your XSD uses ``<xs:include>`` or ``<xs:import>`` with relative paths.
|
|
512
|
+
|
|
513
|
+
Use the ``error_facets`` argument to control which attributes of detected errors will be collected and reported.
|
|
514
|
+
|
|
515
|
+
E.g. the element locator (XPath), error message, involved namespace and/or the XSD validator that failed.
|
|
516
|
+
|
|
517
|
+
Example:
|
|
518
|
+
|
|
519
|
+
.. code:: robotframework
|
|
520
|
+
|
|
521
|
+
**********Library xmlvalidator error_facets=path, message, validator
|
|
522
|
+
|
|
523
|
+
You can also combine this with a preloaded schema and/or a base_url:
|
|
524
|
+
|
|
525
|
+
.. code:: robotframework
|
|
526
|
+
|
|
527
|
+
**********Library xmlvalidator xsd_path=schemas/schema.xsd
|
|
528
|
+
**********... error_facets=value, namespaces
|
|
529
|
+
|
|
530
|
+
For more examples see the project's
|
|
531
|
+
`Robot Framework integration test suite <https://github.com/MichaelHallik/robotframework-xmlvalidator/blob/main/test/integration/01_library_initialization.robot>`_.
|
|
532
|
+
|
|
533
|
+
**Raises**
|
|
534
|
+
|
|
535
|
+
+---------------+--------------------------------------------------------------+
|
|
536
|
+
| Exception | Description |
|
|
537
|
+
+===============+==============================================================+
|
|
538
|
+
| ValueError | Raised if ``xsd_path`` is provided, but resolves to multiple |
|
|
539
|
+
| | XSD files instead of a single one. |
|
|
540
|
+
+---------------+--------------------------------------------------------------+
|
|
541
|
+
| SystemError | Raised if loading the specified XSD schema fails due to an |
|
|
542
|
+
| | invalid or unreadable file. |
|
|
543
|
+
+---------------+--------------------------------------------------------------+
|
|
544
|
+
| IOError | Raised if the XSD file cannot be accessed due to file |
|
|
545
|
+
| | system restrictions. |
|
|
546
|
+
+---------------+--------------------------------------------------------------+
|
|
547
|
+
"""
|
|
548
|
+
# Use composition for flexibility, as helper classes may grow.
|
|
549
|
+
self.validator_utils = ValidatorUtils()
|
|
550
|
+
self.validator_results = ValidatorResultRecorder()
|
|
551
|
+
# Initialize the xsd schema from the xsd_path, if provided.
|
|
552
|
+
if xsd_path:
|
|
553
|
+
# Try to get a single xsd file from the provided xsd_path.
|
|
554
|
+
xsd_file_path, is_single_xsd_file = (
|
|
555
|
+
self.validator_utils.get_file_paths(
|
|
556
|
+
xsd_path, 'xsd'
|
|
557
|
+
)
|
|
558
|
+
)
|
|
559
|
+
# We need (a path to) a single xsd file.
|
|
560
|
+
if not is_single_xsd_file:
|
|
561
|
+
raise ValueError(f"Got multiple xsd files: {xsd_file_path}.")
|
|
562
|
+
# Handle incorrect file extension.
|
|
563
|
+
if xsd_file_path[0].suffix != '.xsd':
|
|
564
|
+
# Raise a load error.
|
|
565
|
+
raise SystemError(
|
|
566
|
+
f"ValueError: {xsd_file_path[0]} is not an XSD file."
|
|
567
|
+
)
|
|
568
|
+
# Try to load the provided XSD file.
|
|
569
|
+
result = self._load_schema(xsd_file_path[0], base_url )
|
|
570
|
+
if result.success:
|
|
571
|
+
# Set the loaded XSD file as default schema.
|
|
572
|
+
self.schema = result.value
|
|
573
|
+
logger.info(
|
|
574
|
+
f"Schema '{self.schema.name}' set.", # type: ignore
|
|
575
|
+
also_console=True)
|
|
576
|
+
else:
|
|
577
|
+
# Or report the load error.
|
|
578
|
+
raise SystemError(f"Loading of schema failed: {result.error}")
|
|
579
|
+
# Or inform the user on what to do.
|
|
580
|
+
else:
|
|
581
|
+
logger.info(
|
|
582
|
+
"No XSD schema set: provide schema(s) during keyword calls.",
|
|
583
|
+
also_console=True
|
|
584
|
+
)
|
|
585
|
+
# And flag the schema attr as None.
|
|
586
|
+
self.schema = None
|
|
587
|
+
# Set the error facets to collect for failed XML validations.
|
|
588
|
+
self.error_facets = error_facets if error_facets else [
|
|
589
|
+
'path', 'reason'
|
|
590
|
+
]
|
|
591
|
+
logger.info(
|
|
592
|
+
f"Collecting error facets: {self.error_facets}.",
|
|
593
|
+
also_console=True
|
|
594
|
+
)
|
|
595
|
+
logger.info("XML Validator ready for use!", also_console=True)
|
|
596
|
+
self.nr_instances += 1
|
|
597
|
+
logger.info(f'Number of library instances: {self.nr_instances}.')
|
|
598
|
+
|
|
599
|
+
def _determine_validations(
|
|
600
|
+
self,
|
|
601
|
+
xml_paths: List[Path],
|
|
602
|
+
xsd_path: Optional[str|Path] = None,
|
|
603
|
+
xsd_search_strategy: Optional[
|
|
604
|
+
Literal['by_namespace', 'by_file_name']
|
|
605
|
+
] = None,
|
|
606
|
+
base_url: Optional[str] = None
|
|
607
|
+
) -> Dict[Path, Path | None]:
|
|
608
|
+
"""
|
|
609
|
+
Constructs a mapping between XML files and the XSD schemas to
|
|
610
|
+
use for their validation.
|
|
611
|
+
|
|
612
|
+
This internal method is used during batch validation workflows
|
|
613
|
+
to determine which XSD schema (if any) should be used for each
|
|
614
|
+
XML file in a given set.
|
|
615
|
+
|
|
616
|
+
Depending on the presence of `xsd_path` and
|
|
617
|
+
`xsd_search_strategy`, it supports the following resolution
|
|
618
|
+
strategies:
|
|
619
|
+
|
|
620
|
+
1. Single schema (shared across all XML files).
|
|
621
|
+
|
|
622
|
+
If `xsd_path` resolves to a single XSD file, that schema is
|
|
623
|
+
preloaded and reused for all target XML files.
|
|
624
|
+
|
|
625
|
+
2. Dynamic schema matching (multiple schemas):
|
|
626
|
+
|
|
627
|
+
If `xsd_path` resolves to a directory with multiple `.xsd`
|
|
628
|
+
files, each XML file is matched to a corresponding schema file.
|
|
629
|
+
|
|
630
|
+
There are two ways in which `xsd_path` can resolve to a
|
|
631
|
+
directory:
|
|
632
|
+
|
|
633
|
+
- Either because the passed `xsd_path` is a directory.
|
|
634
|
+
- Or because no `xsd_path` has been passed (hence it is None),
|
|
635
|
+
but a (valid) `xsd_search_strategy` has been passed. In that
|
|
636
|
+
case it is assumed that the passed `xml_paths` is a list
|
|
637
|
+
holding one item that points to a folder holding not only XML
|
|
638
|
+
files, but also their accompanying XSD files. That path will,
|
|
639
|
+
therefore, be assigned to `xsd_path` within the method.
|
|
640
|
+
|
|
641
|
+
XML and XSD files can be matched by either one of these
|
|
642
|
+
strategies:
|
|
643
|
+
|
|
644
|
+
- 'by_namespace' (DEFAULT):
|
|
645
|
+
Matches XSDs based on XML namespace compatibility.
|
|
646
|
+
- 'by_file_name':
|
|
647
|
+
Matches XML and XSD files based on identical stem (base
|
|
648
|
+
name).
|
|
649
|
+
|
|
650
|
+
3. Fallback to default schema:
|
|
651
|
+
|
|
652
|
+
If neither `xsd_path` nor `xsd_search_strategy` is provided, the
|
|
653
|
+
method assumes a default schema has already been set on the
|
|
654
|
+
class instance (i.e. during library initialization).
|
|
655
|
+
|
|
656
|
+
Args:
|
|
657
|
+
|
|
658
|
+
- xml_paths (List[Path]):
|
|
659
|
+
A list of resolved paths to XML files that need validation.
|
|
660
|
+
- xsd_path (str | Path, optional):
|
|
661
|
+
Either a path to a single `.xsd` file or a directory
|
|
662
|
+
containing multiple `.xsd` files. If not provided, fallback
|
|
663
|
+
behavior applies.
|
|
664
|
+
- xsd_search_strategy (Literal["by_namespace", "by_file_name"], optional):
|
|
665
|
+
Specifies how to dynamically map each XML file to a schema
|
|
666
|
+
when multiple `.xsd` files are present. Required when
|
|
667
|
+
`xsd_path` is a folder.
|
|
668
|
+
- base_url (str, optional):
|
|
669
|
+
An optional base path or URL used when resolving `import` or
|
|
670
|
+
`include` statements within XSD files.
|
|
671
|
+
|
|
672
|
+
Returns:
|
|
673
|
+
|
|
674
|
+
- Dict[Path, Path | None]:
|
|
675
|
+
A dictionary mapping each XML file path to its corresponding
|
|
676
|
+
XSD schema path (or to None if the default schema should be
|
|
677
|
+
used).
|
|
678
|
+
|
|
679
|
+
Raises:
|
|
680
|
+
|
|
681
|
+
- SystemError:
|
|
682
|
+
Raised if schema loading fails during single-schema or dynamic
|
|
683
|
+
resolution. The raised error includes parsing failure details.
|
|
684
|
+
|
|
685
|
+
Notes:
|
|
686
|
+
|
|
687
|
+
- If an XML file cannot be matched to a schema in dynamic
|
|
688
|
+
matching mode, a `FileNotFoundError` is mapped to that file
|
|
689
|
+
path in the return dictionary.
|
|
690
|
+
- If a malformed XMl file is encountered, the resulting parse
|
|
691
|
+
error will be caught and mapped to the path of that file, in the
|
|
692
|
+
return dictionary.
|
|
693
|
+
- This method does not perform validation — only resolution of
|
|
694
|
+
schema-to-XML associations.
|
|
695
|
+
- The returned result is passed to `_validate_xml()` during
|
|
696
|
+
batch validation to apply the resolved schema to each XML.
|
|
697
|
+
"""
|
|
698
|
+
# Initialize the return dict.
|
|
699
|
+
validations: dict[Path, Path | None] = {}
|
|
700
|
+
# If no xsd_path is provided, but an xsd_search_strategy IS provided.
|
|
701
|
+
if not xsd_path and xsd_search_strategy:
|
|
702
|
+
# Then assume the XSD file(s) to reside in the XML folder
|
|
703
|
+
xsd_path = xml_paths[0].parent
|
|
704
|
+
# The xsd_path can be either an XSD file or a dir holding the file(s).
|
|
705
|
+
if xsd_path:
|
|
706
|
+
# Determine and resolve the XSD file path(s).
|
|
707
|
+
xsd_paths, is_single_xsd_file = (
|
|
708
|
+
self.validator_utils.get_file_paths(xsd_path, 'xsd')
|
|
709
|
+
)
|
|
710
|
+
# All XML files are to be validated against a single XSD schema.
|
|
711
|
+
if is_single_xsd_file:
|
|
712
|
+
# Load the schema as default schema to use.
|
|
713
|
+
result = self._ensure_schema(xsd_paths[0], base_url)
|
|
714
|
+
# Handle a load error.
|
|
715
|
+
if not result.success:
|
|
716
|
+
raise SystemError(
|
|
717
|
+
f"Loading of schema failed: {result.error}."
|
|
718
|
+
)
|
|
719
|
+
# Ensure downstream schema loading will be skipped.
|
|
720
|
+
xsd_file_paths: List[Path|None] = [None] * len(xml_paths)
|
|
721
|
+
# Create the XML/XSD map.
|
|
722
|
+
validations = dict(
|
|
723
|
+
zip( xml_paths, xsd_file_paths )
|
|
724
|
+
)
|
|
725
|
+
# We got a set of XSDs, that need to be dynamically mapped to XMLs.
|
|
726
|
+
else:
|
|
727
|
+
# Map the XSD files to the XML files.
|
|
728
|
+
validations = self._find_schemas(
|
|
729
|
+
xml_paths,
|
|
730
|
+
xsd_paths,
|
|
731
|
+
xsd_search_strategy if xsd_search_strategy else 'by_namespace',
|
|
732
|
+
base_url
|
|
733
|
+
)
|
|
734
|
+
# No XSD path or search strategy given: assume schema set during init.
|
|
735
|
+
else:
|
|
736
|
+
# Ensure default schema is loaded; raises exception otherwise.
|
|
737
|
+
result = self._ensure_schema(None, None)
|
|
738
|
+
# Ensure downstream schema loading will now be skipped.
|
|
739
|
+
xsd_file_paths: List[Path|None] = [None] * len(xml_paths)
|
|
740
|
+
# Create the XML/XSD map.
|
|
741
|
+
validations = dict(
|
|
742
|
+
zip( xml_paths, xsd_file_paths )
|
|
743
|
+
)
|
|
744
|
+
return validations
|
|
745
|
+
|
|
746
|
+
def _ensure_schema(
|
|
747
|
+
self,
|
|
748
|
+
xsd_path: Optional[Path] = None,
|
|
749
|
+
base_url: Optional[str] = None
|
|
750
|
+
) -> ValidatorResult:
|
|
751
|
+
"""
|
|
752
|
+
Ensures that a schema is available for validation.
|
|
753
|
+
|
|
754
|
+
This internal method guarantees that the `XmlValidator` instance
|
|
755
|
+
has access to an XSD schema.
|
|
756
|
+
|
|
757
|
+
It checks whether a schema is already loaded (in `self.schema`)
|
|
758
|
+
or whether one should be loaded from the provided `xsd_path`.
|
|
759
|
+
|
|
760
|
+
If no `xsd_path` is given but a schema is already loaded (in
|
|
761
|
+
self.schema), that existing schema is simply reused.
|
|
762
|
+
|
|
763
|
+
If a new path is passed, it is always loaded via
|
|
764
|
+
`_load_schema()`, replacing an existing schema (if any).
|
|
765
|
+
|
|
766
|
+
If no `xsd_path` is given and no schema was loaded earlier
|
|
767
|
+
(self.schema is None), the method raises a ValueError.
|
|
768
|
+
|
|
769
|
+
Args:
|
|
770
|
+
|
|
771
|
+
- xsd_path (str or Path, optional):
|
|
772
|
+
Path to the XSD schema to load and set as default. Overrides
|
|
773
|
+
any previously loaded schema.
|
|
774
|
+
|
|
775
|
+
- base_url (str, optional):
|
|
776
|
+
Optional base path used for resolving `import` and `include`
|
|
777
|
+
statements in the schema.
|
|
778
|
+
|
|
779
|
+
Returns:
|
|
780
|
+
|
|
781
|
+
- ValidatorResult:
|
|
782
|
+
A result object indicating whether a valid schema is available
|
|
783
|
+
for XML validation.
|
|
784
|
+
- If `success=True`:
|
|
785
|
+
- The `value` field holds the loaded `XMLSchema` object from
|
|
786
|
+
the `xmlschema` library.
|
|
787
|
+
- This schema can be used to validate one or more XML files
|
|
788
|
+
and will typically be assigned to `self.schema`.
|
|
789
|
+
- If `success=False`:
|
|
790
|
+
- The `error` field contains structured information about
|
|
791
|
+
the reason for failure (e.g., schema file not found, parse
|
|
792
|
+
error).
|
|
793
|
+
|
|
794
|
+
Raises:
|
|
795
|
+
|
|
796
|
+
- ValueError:
|
|
797
|
+
- Raised if neither a schema has been preloaded nor an
|
|
798
|
+
`xsd_path` is provided. In such cases, the method cannot
|
|
799
|
+
proceed and explicitly requires a schema to be available.
|
|
800
|
+
- Note: other schema-related failures (e.g., parsing errors,
|
|
801
|
+
I/O issues) are not raised here. Any such failures are
|
|
802
|
+
captured by _load_schema and then returned as part of a
|
|
803
|
+
`ValidatorResult` object.
|
|
804
|
+
|
|
805
|
+
Notes:
|
|
806
|
+
|
|
807
|
+
- This method does not raise exceptions directly; it wraps
|
|
808
|
+
schema loading results using `ValidatorResult`.
|
|
809
|
+
- Used internally in validation and schema resolution workflows.
|
|
810
|
+
- This method relies on `_load_schema` for the actual schema
|
|
811
|
+
loading process, which validates and parses the XSD schema.
|
|
812
|
+
"""
|
|
813
|
+
if not (self.schema or xsd_path):
|
|
814
|
+
raise ValueError(
|
|
815
|
+
"No schema: provide an XSD path during keyword call(s)."
|
|
816
|
+
)
|
|
817
|
+
if self.schema and not xsd_path :
|
|
818
|
+
logger.info(
|
|
819
|
+
f'No new schema set: keeping existing schema {self.schema}.'
|
|
820
|
+
)
|
|
821
|
+
return ValidatorResult(success=True, value=self.schema)
|
|
822
|
+
if not self.schema and xsd_path:
|
|
823
|
+
logger.info(f'Setting schema file: {xsd_path}.', also_console=True)
|
|
824
|
+
if self.schema and xsd_path:
|
|
825
|
+
logger.info(
|
|
826
|
+
f'Setting new schema file: {xsd_path}.',
|
|
827
|
+
also_console=True
|
|
828
|
+
)
|
|
829
|
+
return self._load_schema(xsd_path, base_url) # pyright: ignore
|
|
830
|
+
|
|
831
|
+
def _find_schemas(
|
|
832
|
+
self,
|
|
833
|
+
xml_file_paths: List[Path],
|
|
834
|
+
xsd_file_paths: List[Path],
|
|
835
|
+
search_by: Literal[
|
|
836
|
+
'by_namespace',
|
|
837
|
+
'by_file_name'
|
|
838
|
+
] = 'by_namespace',
|
|
839
|
+
base_url: Optional[str] = None
|
|
840
|
+
) -> Dict[ Path, Path | None ]:
|
|
841
|
+
"""
|
|
842
|
+
Finds matching XSD schemas for XML files using the specified
|
|
843
|
+
search strategy.
|
|
844
|
+
|
|
845
|
+
This internal method performs schema-to-XML matching for batch
|
|
846
|
+
validation workflows, returning a dictionary that maps each XML
|
|
847
|
+
file to its corresponding XSD file. If no match is found for a
|
|
848
|
+
given file, that file is mapped to `None`.
|
|
849
|
+
|
|
850
|
+
Two matching strategies are supported:
|
|
851
|
+
|
|
852
|
+
1. by_namespace (default):
|
|
853
|
+
- Extracts the set of declared namespaces from each XML file.
|
|
854
|
+
- Compares them to the `targetNamespace` declaration (and
|
|
855
|
+
related declarations) of/in each XSD file.
|
|
856
|
+
- Matches the first schema that shares at least one
|
|
857
|
+
namespace.
|
|
858
|
+
2. by_file_name:
|
|
859
|
+
- Matches based on file name stem (i.e. file name without
|
|
860
|
+
extension), e.g., `invoice.xml` ↔ `invoice.xsd`.
|
|
861
|
+
- Matches the first schema with a matching base name (if
|
|
862
|
+
any).
|
|
863
|
+
|
|
864
|
+
If an XML file cannot be matched to a schema in dynamic matching
|
|
865
|
+
mode, a `FileNotFoundError` is mapped to that file path in the
|
|
866
|
+
return dictionary.
|
|
867
|
+
|
|
868
|
+
If a malformed XMl file is encountered, the resulting parse
|
|
869
|
+
error will be caught and mapped to the path of that file, in the
|
|
870
|
+
return dictionary.
|
|
871
|
+
|
|
872
|
+
Args:
|
|
873
|
+
|
|
874
|
+
- xml_file_paths (List[Path]):
|
|
875
|
+
A list of paths to XML files to be matched with schemas.
|
|
876
|
+
- xsd_file_paths (List[Path]):
|
|
877
|
+
A list of paths to candidate XSD schema files.
|
|
878
|
+
- search_by (Literal["by_namespace", "by_file_name"], optional):
|
|
879
|
+
The strategy to use for matching XML files to schemas.
|
|
880
|
+
Defaults to `"by_namespace"`.
|
|
881
|
+
- base_url (str, optional):
|
|
882
|
+
An optional base URL used to resolve imports and includes
|
|
883
|
+
during namespace-based schema parsing.
|
|
884
|
+
|
|
885
|
+
Returns:
|
|
886
|
+
|
|
887
|
+
- Dict[Path, Path | None | FileNotFoundError]:
|
|
888
|
+
A mapping from each XML file path to:
|
|
889
|
+
- A matching XSD file path if found.
|
|
890
|
+
- None, if no match is applicable.
|
|
891
|
+
- A `FileNotFoundError` object if the schema lookup failed due
|
|
892
|
+
to loading or parsing errors.
|
|
893
|
+
|
|
894
|
+
Notes:
|
|
895
|
+
|
|
896
|
+
- This method does not perform validation — it only establishes
|
|
897
|
+
schema associations, which are later consumed by
|
|
898
|
+
`_validate_xml()`.
|
|
899
|
+
- Errors during XML parsing or namespace extraction are caught
|
|
900
|
+
and logged and the corresponding file is assigned `None`.
|
|
901
|
+
- XSD schema loading failures (e.g., invalid file, parse error)
|
|
902
|
+
result in a `FileNotFoundError` marker.
|
|
903
|
+
"""
|
|
904
|
+
# Prepare the return dictionary.
|
|
905
|
+
validations = {}
|
|
906
|
+
# For each XML file, try to find a matching XSD file.
|
|
907
|
+
logger.info(
|
|
908
|
+
f"Mapping XML files to schemata {search_by.replace('_', ' ')}.",
|
|
909
|
+
also_console=True
|
|
910
|
+
)
|
|
911
|
+
for xml_file_path in xml_file_paths:
|
|
912
|
+
logger.info(f"\tSearching schema for: {xml_file_path.stem}.")
|
|
913
|
+
# Initialize the dict entry with None.
|
|
914
|
+
validations[xml_file_path] = None
|
|
915
|
+
# Namespace-based matching.
|
|
916
|
+
if search_by == "by_namespace":
|
|
917
|
+
# Get the XML's namespace(s).
|
|
918
|
+
try:
|
|
919
|
+
# Get the XML root element.
|
|
920
|
+
xml_root = etree.parse( # pylint: disable=I1101:c-extension-no-member
|
|
921
|
+
str(xml_file_path),
|
|
922
|
+
parser=etree.XMLParser() # pylint: disable=I1101:c-extension-no-member
|
|
923
|
+
).getroot()
|
|
924
|
+
# Get the namespaces from the root element.
|
|
925
|
+
xml_namespaces = ValidatorUtils.extract_xml_namespaces(
|
|
926
|
+
xml_root,
|
|
927
|
+
include_nested=False
|
|
928
|
+
)
|
|
929
|
+
# Catch and handle any exception.
|
|
930
|
+
except Exception as err: # pylint: disable=W0718:broad-exception-caught
|
|
931
|
+
# Inform the user.
|
|
932
|
+
logger.warn('Processing XML file failed.')
|
|
933
|
+
# Collect the error to be mapped to the xml file.
|
|
934
|
+
validations[xml_file_path] = err
|
|
935
|
+
continue
|
|
936
|
+
# Test each XSD file for a matching namespace.
|
|
937
|
+
for xsd_file_path in xsd_file_paths:
|
|
938
|
+
logger.info(f"Testing schema: {xsd_file_path}.")
|
|
939
|
+
# Load the schema.
|
|
940
|
+
result = self._load_schema(
|
|
941
|
+
xsd_file_path, base_url=base_url
|
|
942
|
+
)
|
|
943
|
+
# Log an error, and continue (instead of failing).
|
|
944
|
+
if not result.success:
|
|
945
|
+
logger.warn(
|
|
946
|
+
f"Matching attempt failed due to exception: {result.error}."
|
|
947
|
+
)
|
|
948
|
+
continue
|
|
949
|
+
# Use match_namespace_to_schema for matching logic.
|
|
950
|
+
match = self.validator_utils.match_namespace_to_schema(
|
|
951
|
+
result.value, # type: ignore
|
|
952
|
+
xml_namespaces, # type: ignore
|
|
953
|
+
)
|
|
954
|
+
# If succesful, flag the xsd file as match & end loop.
|
|
955
|
+
if match:
|
|
956
|
+
logger.info(f"\t\t\tMatch found with: {xsd_file_path}.")
|
|
957
|
+
validations[xml_file_path] = xsd_file_path
|
|
958
|
+
break
|
|
959
|
+
# Filename-based matching.
|
|
960
|
+
elif search_by == "by_file_name":
|
|
961
|
+
for xsd_file_path in xsd_file_paths:
|
|
962
|
+
# Log informative.
|
|
963
|
+
logger.info(f"\t\t\tTesting file name: {xsd_file_path}.")
|
|
964
|
+
# Compare the XSD/XML file names.
|
|
965
|
+
if xsd_file_path.stem == xml_file_path.stem:
|
|
966
|
+
logger.info("\t\t\tFound match.")
|
|
967
|
+
# Assign current XSD path in case of a match & break.
|
|
968
|
+
validations[xml_file_path] = xsd_file_path
|
|
969
|
+
break
|
|
970
|
+
# Locating matching schema failed: go to the next xsd file.
|
|
971
|
+
logger.info("\t\t\tNo match: trying next schema file.")
|
|
972
|
+
continue
|
|
973
|
+
# Handle unsupported/incorrect search strategy.
|
|
974
|
+
else:
|
|
975
|
+
raise ValueError(f"Unsupported search strategy: {search_by}.")
|
|
976
|
+
# If no match has been found.
|
|
977
|
+
if not validations[xml_file_path]:
|
|
978
|
+
logger.info(f"No valid XSD found for {xml_file_path}.")
|
|
979
|
+
validations[xml_file_path] = FileNotFoundError(
|
|
980
|
+
f'No matching XSD found for: {xml_file_path.stem}.'
|
|
981
|
+
)
|
|
982
|
+
return validations
|
|
983
|
+
|
|
984
|
+
def _load_schema(
|
|
985
|
+
self,
|
|
986
|
+
xsd_path: Path,
|
|
987
|
+
base_url: Optional[str] = None
|
|
988
|
+
) -> ValidatorResult:
|
|
989
|
+
"""
|
|
990
|
+
This method is responsible for initializing a schema object,
|
|
991
|
+
using the `xmlschema` library.
|
|
992
|
+
|
|
993
|
+
As such, it parses and loads an XSD schema from a given file
|
|
994
|
+
path, with support for resolving relative paths via the
|
|
995
|
+
`base_url` parameter.
|
|
996
|
+
|
|
997
|
+
If the parse/load attempt of a schema file results in any error
|
|
998
|
+
(e.g. a parse error), that error will be caught, wrapped in a
|
|
999
|
+
ValidatorResult object and returned. See for the latter the
|
|
1000
|
+
'Returns' section below.
|
|
1001
|
+
|
|
1002
|
+
Args:
|
|
1003
|
+
|
|
1004
|
+
- xsd_path (Path):
|
|
1005
|
+
Path to a `.xsd` schema file to load.
|
|
1006
|
+
- base_url (str, optional):
|
|
1007
|
+
An optional base path or URL for resolving `import` and
|
|
1008
|
+
`include` references inside the XSD schema.
|
|
1009
|
+
|
|
1010
|
+
Returns:
|
|
1011
|
+
|
|
1012
|
+
- ValidatorResult:
|
|
1013
|
+
A result object indicating whether schema loading succeeded.
|
|
1014
|
+
- If `success=True`:
|
|
1015
|
+
- The `value` field contains a fully parsed `XMLSchema`
|
|
1016
|
+
object.
|
|
1017
|
+
- If `success=False`:
|
|
1018
|
+
- The `error` field contains structured error information
|
|
1019
|
+
describing what failed and why (e.g., file unreadable, not
|
|
1020
|
+
well-formed, parse error, etc.).
|
|
1021
|
+
|
|
1022
|
+
Notes:
|
|
1023
|
+
|
|
1024
|
+
- This method never raises exceptions directly; all errors are
|
|
1025
|
+
captured and wrapped in a `ValidatorResult`.
|
|
1026
|
+
- Used internally by `_ensure_schema()` and `_find_schemas()` to
|
|
1027
|
+
guarantee schema readiness.
|
|
1028
|
+
"""
|
|
1029
|
+
# Load the XSD schema (with base_url, if provided).
|
|
1030
|
+
try:
|
|
1031
|
+
self.schema = XMLSchema( xsd_path, base_url=base_url )
|
|
1032
|
+
return ValidatorResult( success=True, value=self.schema )
|
|
1033
|
+
# Catcherrors to propagate them upstream.
|
|
1034
|
+
except Exception as e: # pylint: disable=W0718:broad-exception-caught
|
|
1035
|
+
return ValidatorResult(
|
|
1036
|
+
success=False, error={"XMLSchemaValidationError": e}
|
|
1037
|
+
)
|
|
1038
|
+
|
|
1039
|
+
def _validate_xml(self, # pylint: disable=R0913:too-many-arguments disable=R0917:too-many-positional-arguments
|
|
1040
|
+
xml_file_path: Path,
|
|
1041
|
+
xsd_file_path: Optional[Path] = None,
|
|
1042
|
+
base_url: Optional[str] = None,
|
|
1043
|
+
error_facets: Optional[ List[str] ] = None,
|
|
1044
|
+
pre_parse: Optional[bool] = True
|
|
1045
|
+
) -> Tuple[
|
|
1046
|
+
bool, Optional[
|
|
1047
|
+
List[ dict[str, Any] ]
|
|
1048
|
+
]
|
|
1049
|
+
]:
|
|
1050
|
+
"""
|
|
1051
|
+
Validates an XML file against the currently loaded or provided
|
|
1052
|
+
XSD schema.
|
|
1053
|
+
|
|
1054
|
+
This method performs the core XML validation logic and is
|
|
1055
|
+
invoked by the public `validate_xml_file` method. It checks an
|
|
1056
|
+
XML file for conformance with the structural and datatype rules
|
|
1057
|
+
defined in the XSD schema.
|
|
1058
|
+
|
|
1059
|
+
It focuses exclusively on validation and generating errors based
|
|
1060
|
+
on the specified facets.
|
|
1061
|
+
|
|
1062
|
+
Errors that are collected and returned can be categorized as
|
|
1063
|
+
follows:
|
|
1064
|
+
|
|
1065
|
+
1. XSD Schema violations.
|
|
1066
|
+
|
|
1067
|
+
The following types of XSD schema violations are detected during
|
|
1068
|
+
validation:
|
|
1069
|
+
|
|
1070
|
+
1. Detects missing or extra elements that violate cardinality
|
|
1071
|
+
rules, e.g.:
|
|
1072
|
+
- Verifies that all mandatory elements (minOccurs > 0) are
|
|
1073
|
+
present in the XML.
|
|
1074
|
+
- Ensures that optional elements (minOccurs = 0) do not
|
|
1075
|
+
exceed their maximum allowed occurrences (maxOccurs).
|
|
1076
|
+
|
|
1077
|
+
2. Sequence and Order Violations:
|
|
1078
|
+
- Validates the order of child elements within a parent
|
|
1079
|
+
element if the schema specifies a sequence model
|
|
1080
|
+
(`<xsd:sequence>`).
|
|
1081
|
+
- Detects elements that are out of order or missing in a
|
|
1082
|
+
sequence.
|
|
1083
|
+
|
|
1084
|
+
3. Datatype Violations:
|
|
1085
|
+
- Ensures that element and attribute values conform to their
|
|
1086
|
+
specified datatypes (e.g., xs:string, xs:integer,
|
|
1087
|
+
xs:dateTime).
|
|
1088
|
+
- Identifies invalid formats, such as incorrect date or time
|
|
1089
|
+
formats for xs:date and xs:dateTime.
|
|
1090
|
+
|
|
1091
|
+
4. Pattern and Enumeration Violations:
|
|
1092
|
+
- Checks that values conform to patterns defined using
|
|
1093
|
+
`<xsd:pattern>`.
|
|
1094
|
+
- Ensures that values fall within allowed enumerations
|
|
1095
|
+
specified in the schema.
|
|
1096
|
+
|
|
1097
|
+
5. Attribute Validation:
|
|
1098
|
+
- Verifies that required attributes are present.
|
|
1099
|
+
- Ensures that attribute values adhere to their declared
|
|
1100
|
+
datatypes and constraints.
|
|
1101
|
+
|
|
1102
|
+
6. Namespace Compliance:
|
|
1103
|
+
- Validates that elements and attributes belong to the
|
|
1104
|
+
correct namespaces as defined in the schema.
|
|
1105
|
+
- Detects namespace mismatches or missing namespace
|
|
1106
|
+
declarations.
|
|
1107
|
+
|
|
1108
|
+
7. Group Model Violations:
|
|
1109
|
+
- Validates conformance with `<xsd:choice>` and `<xsd:all>`
|
|
1110
|
+
group models, ensuring correct usage of child elements as
|
|
1111
|
+
per the schema.
|
|
1112
|
+
|
|
1113
|
+
8. Referential Constraints:
|
|
1114
|
+
- Checks for violations in `<xsd:key>`, `<xsd:keyref>`, and
|
|
1115
|
+
`<xsd:unique>` constraints.
|
|
1116
|
+
|
|
1117
|
+
9. Document Structure and Completeness:
|
|
1118
|
+
- Ensures that the XML document adheres to the hierarchical
|
|
1119
|
+
structure defined by the schema.
|
|
1120
|
+
- Detects incomplete or improperly nested elements.
|
|
1121
|
+
|
|
1122
|
+
10. General Schema Violations:
|
|
1123
|
+
- Detects schema-level issues, such as invalid imports or
|
|
1124
|
+
includes, during schema compilation if they affect
|
|
1125
|
+
validation.
|
|
1126
|
+
|
|
1127
|
+
2. Errors following from malformed XML and/or XSD files.
|
|
1128
|
+
|
|
1129
|
+
The method checks whether upstream handling of the involved
|
|
1130
|
+
XML file (e.g. during dynamic schema matching) resulted in an
|
|
1131
|
+
error. For instance a FileNotFound error or a ParseError.
|
|
1132
|
+
|
|
1133
|
+
The method does this by checking whether the coresponding
|
|
1134
|
+
xsd_file_path is an instance of BaseException. In that case,
|
|
1135
|
+
the method returns early, propagating the error to the
|
|
1136
|
+
downstream error collection & reporting.
|
|
1137
|
+
|
|
1138
|
+
The method tself performs a additonal sanity check on the
|
|
1139
|
+
involved XML/XSD files, calling the sanity_check_files()
|
|
1140
|
+
utility method on both. The latter checks each file whether:
|
|
1141
|
+
|
|
1142
|
+
- it exists
|
|
1143
|
+
- is not empty
|
|
1144
|
+
- is of the correct file type
|
|
1145
|
+
- is well-formed (syntactycally correct)
|
|
1146
|
+
|
|
1147
|
+
In case of a failing check, the method returns early,
|
|
1148
|
+
propagating the involved error to downstream error collection
|
|
1149
|
+
& reporting.
|
|
1150
|
+
|
|
1151
|
+
3. Invalid XSD schema.
|
|
1152
|
+
|
|
1153
|
+
If schema loading fails, the method returns early,
|
|
1154
|
+
propagating the involved error to downstream error collection
|
|
1155
|
+
& reporting.
|
|
1156
|
+
|
|
1157
|
+
Args:
|
|
1158
|
+
|
|
1159
|
+
- xml_path (str or Path):
|
|
1160
|
+
Path to the XML file to validate.
|
|
1161
|
+
|
|
1162
|
+
- xsd_path (str or Path, optional):
|
|
1163
|
+
Path to an alternative XSD schema file to use for this
|
|
1164
|
+
validation. If not provided, the schema loaded during
|
|
1165
|
+
initialization will be used.
|
|
1166
|
+
|
|
1167
|
+
- error_facets (list, optional):
|
|
1168
|
+
Specifies which attributes of the XMLSchemaValidationError
|
|
1169
|
+
should be included in the error output.
|
|
1170
|
+
This provides flexibility in tailoring the level of detail in
|
|
1171
|
+
the error messages.
|
|
1172
|
+
|
|
1173
|
+
Supported Attributes:
|
|
1174
|
+
|
|
1175
|
+
add_note, args, elem, expected, get_elem_as_string,
|
|
1176
|
+
get_obj_as_string, index, invalid_child, invalid_tag,
|
|
1177
|
+
message, msg, namespaces, obj, occurs, origin_url,
|
|
1178
|
+
particle, path, reason, root, schema_url, source,
|
|
1179
|
+
sourceline, stack_trace, validator, with_traceback
|
|
1180
|
+
|
|
1181
|
+
Defaults to ['path', 'reason'].
|
|
1182
|
+
|
|
1183
|
+
Returns:
|
|
1184
|
+
|
|
1185
|
+
- tuple (is_valid, errors):
|
|
1186
|
+
- is_valid (bool):
|
|
1187
|
+
True if the XML file conforms to the schema, False
|
|
1188
|
+
otherwise.
|
|
1189
|
+
- errors (list of dict):
|
|
1190
|
+
A list of detailed error facets/aspects, each represented as
|
|
1191
|
+
dictionary. The dictionary keys depend on the `error_facets`
|
|
1192
|
+
argument.
|
|
1193
|
+
"""
|
|
1194
|
+
# Log informative.
|
|
1195
|
+
logger.info(
|
|
1196
|
+
f"Validating '{xml_file_path.stem}'.", also_console=True
|
|
1197
|
+
)
|
|
1198
|
+
# Check upstream XSD matching led to an err pertaining to the XML.
|
|
1199
|
+
if isinstance(xsd_file_path, BaseException):
|
|
1200
|
+
return False, [{
|
|
1201
|
+
facet: str(xsd_file_path) if facet == 'reason' else ''
|
|
1202
|
+
for facet in (error_facets or self.error_facets)
|
|
1203
|
+
}]
|
|
1204
|
+
# Sanity check the target (XML/XSD) files.
|
|
1205
|
+
sanity_check_result = self.validator_utils.sanity_check_files(
|
|
1206
|
+
[file_path for file_path in [
|
|
1207
|
+
xml_file_path, xsd_file_path
|
|
1208
|
+
] if isinstance(file_path, Path) and file_path],
|
|
1209
|
+
base_url=base_url,
|
|
1210
|
+
parse_files=pre_parse
|
|
1211
|
+
)
|
|
1212
|
+
if not sanity_check_result.success:
|
|
1213
|
+
# Abort validation if one or more sanity checks failed.
|
|
1214
|
+
logger.warn("File(s) failed basic sanity check.")
|
|
1215
|
+
return False, sanity_check_result.error
|
|
1216
|
+
# Ensure a valid schema is loaded.
|
|
1217
|
+
loading_result = self._ensure_schema(
|
|
1218
|
+
xsd_file_path, base_url
|
|
1219
|
+
)
|
|
1220
|
+
if not loading_result.success:
|
|
1221
|
+
# Abort the validation if schema loading failed.
|
|
1222
|
+
logger.warn("Schema loading failed.")
|
|
1223
|
+
return False, loading_result.error
|
|
1224
|
+
# Validate the XML against the XSD schema.
|
|
1225
|
+
errors = [
|
|
1226
|
+
{
|
|
1227
|
+
# Collect the details/facets for each XSD violation.
|
|
1228
|
+
facet: getattr(err, facet, None)
|
|
1229
|
+
# Error facets to collect determined by arg or instance.
|
|
1230
|
+
for facet in (error_facets or self.error_facets)
|
|
1231
|
+
}
|
|
1232
|
+
# Generate an err obj (with err details) per encountered violation.
|
|
1233
|
+
for err in loading_result.value.iter_errors(xml_file_path) # pyright: ignore
|
|
1234
|
+
]
|
|
1235
|
+
# Determine validity based on the presence of errors.
|
|
1236
|
+
return (True, None) if len(errors) == 0 else (False, errors)
|
|
1237
|
+
|
|
1238
|
+
@keyword
|
|
1239
|
+
def get_error_facets(self) -> List[str]:
|
|
1240
|
+
"""
|
|
1241
|
+
.. raw:: html
|
|
1242
|
+
|
|
1243
|
+
<span style="text-decoration: underline; font-size: 15px;">Description</span>
|
|
1244
|
+
|
|
1245
|
+
Returns the currently configured error facets.
|
|
1246
|
+
|
|
1247
|
+
Error facets determine which attributes are extracted from
|
|
1248
|
+
validation errors (instances of XMLSchemaValidationError).
|
|
1249
|
+
|
|
1250
|
+
These attributes control the structure and detail level of the
|
|
1251
|
+
error dictionaries returned during XML validation.
|
|
1252
|
+
|
|
1253
|
+
.. raw:: html
|
|
1254
|
+
|
|
1255
|
+
<span style="text-decoration: underline; font-size: 15px;">Returns</span>
|
|
1256
|
+
|
|
1257
|
+
A list of active error facets, e.g. ["path", "reason"].
|
|
1258
|
+
"""
|
|
1259
|
+
return self.error_facets
|
|
1260
|
+
|
|
1261
|
+
@keyword
|
|
1262
|
+
def get_schema(
|
|
1263
|
+
self,
|
|
1264
|
+
return_schema_name: bool = True
|
|
1265
|
+
) -> Optional[str|XMLSchema]:
|
|
1266
|
+
"""
|
|
1267
|
+
.. raw:: html
|
|
1268
|
+
|
|
1269
|
+
<span style="text-decoration: underline; font-size: 15px;">Description</span>
|
|
1270
|
+
|
|
1271
|
+
Returns the currently loaded schema.
|
|
1272
|
+
|
|
1273
|
+
If no schema is loaded, returns None.
|
|
1274
|
+
|
|
1275
|
+
Otherwise, returns either the schema's `name` or the full schema
|
|
1276
|
+
object, depending on the `return_schema_name` flag.
|
|
1277
|
+
|
|
1278
|
+
.. raw:: html
|
|
1279
|
+
|
|
1280
|
+
<span style="text-decoration: underline; font-size: 15px;">Arguments</span>
|
|
1281
|
+
|
|
1282
|
+
return_schema_name:
|
|
1283
|
+
|
|
1284
|
+
- If True (default), returns the schema's name attribute.
|
|
1285
|
+
- If False, returns the actual XMLSchema object.
|
|
1286
|
+
|
|
1287
|
+
.. raw:: html
|
|
1288
|
+
|
|
1289
|
+
<span style="text-decoration: underline; font-size: 15px;">Returns</span>
|
|
1290
|
+
|
|
1291
|
+
The name of the loaded schema, the schema object itself, or None
|
|
1292
|
+
if no schema is available.
|
|
1293
|
+
"""
|
|
1294
|
+
if not self.schema:
|
|
1295
|
+
return None
|
|
1296
|
+
if return_schema_name:
|
|
1297
|
+
return getattr(self.schema, "name", None)
|
|
1298
|
+
return self.schema
|
|
1299
|
+
|
|
1300
|
+
@keyword
|
|
1301
|
+
def log_schema(self, log_name: bool = True):
|
|
1302
|
+
"""
|
|
1303
|
+
.. raw:: html
|
|
1304
|
+
|
|
1305
|
+
<span style="text-decoration: underline; font-size: 15px;">Description</span>
|
|
1306
|
+
|
|
1307
|
+
Prints schema information to the console and writes it to the
|
|
1308
|
+
Robot Framework log.
|
|
1309
|
+
|
|
1310
|
+
If `log_name` is True, the schema's name is printed (if
|
|
1311
|
+
available); otherwise, the full schema object is logged.
|
|
1312
|
+
|
|
1313
|
+
.. raw:: html
|
|
1314
|
+
|
|
1315
|
+
<span style="text-decoration: underline; font-size: 15px;">Arguments</span>
|
|
1316
|
+
|
|
1317
|
+
log_name:
|
|
1318
|
+
|
|
1319
|
+
- If True (default), log only the schema's name.
|
|
1320
|
+
- If False, log the full XMLSchema object.
|
|
1321
|
+
"""
|
|
1322
|
+
if self.schema and log_name:
|
|
1323
|
+
logger.info(
|
|
1324
|
+
f"Schema currently loaded: {self.schema.name}.",
|
|
1325
|
+
also_console=True
|
|
1326
|
+
)
|
|
1327
|
+
logger.info(
|
|
1328
|
+
f"Schema currently loaded: {self.schema}.",
|
|
1329
|
+
also_console=True
|
|
1330
|
+
)
|
|
1331
|
+
|
|
1332
|
+
@keyword
|
|
1333
|
+
def reset_error_facets(self):
|
|
1334
|
+
"""
|
|
1335
|
+
Resets the error facets to their default values.
|
|
1336
|
+
|
|
1337
|
+
By default, only the 'path' and 'reason' attributes of
|
|
1338
|
+
validation errors are collected. This method discards any
|
|
1339
|
+
customizations and reverts to those defaults.
|
|
1340
|
+
|
|
1341
|
+
Prints the the change to the console and in the Robot Framework
|
|
1342
|
+
log.
|
|
1343
|
+
"""
|
|
1344
|
+
self.error_facets = ['path', 'reason']
|
|
1345
|
+
logger.info(
|
|
1346
|
+
f"Error facets restored to default: {', '.join(self.error_facets)}.",
|
|
1347
|
+
also_console=True
|
|
1348
|
+
)
|
|
1349
|
+
|
|
1350
|
+
@keyword
|
|
1351
|
+
def reset_errors(self):
|
|
1352
|
+
"""
|
|
1353
|
+
Clears all previously stored validation results.
|
|
1354
|
+
|
|
1355
|
+
This keyword resets the internal `ValidatorResultRecorder`
|
|
1356
|
+
instance, discarding any errors, warnings, or file status data
|
|
1357
|
+
collected during validation.
|
|
1358
|
+
|
|
1359
|
+
A confirmation message is logged to the Robot Framework log.
|
|
1360
|
+
"""
|
|
1361
|
+
self.validator_results.reset()
|
|
1362
|
+
logger.info("Error collector has been reset.", also_console=True)
|
|
1363
|
+
|
|
1364
|
+
@keyword
|
|
1365
|
+
def reset_schema(self):
|
|
1366
|
+
"""
|
|
1367
|
+
Unloads the currently loaded schema.
|
|
1368
|
+
|
|
1369
|
+
This keyword clears the cached schema reference by setting it to
|
|
1370
|
+
None. Future validation calls must provide a new schema.
|
|
1371
|
+
|
|
1372
|
+
A message confirming schema reset is logged to the Robot Framework
|
|
1373
|
+
log.
|
|
1374
|
+
"""
|
|
1375
|
+
self.schema = None
|
|
1376
|
+
logger.info("Schema attribute reset: no schema loaded.", also_console=True)
|
|
1377
|
+
|
|
1378
|
+
@keyword
|
|
1379
|
+
def validate_xml_files( # pylint: disable=R0913:too-many-arguments disable=R0914:too-many-locals disable=R0917:too-many-positional-arguments
|
|
1380
|
+
self,
|
|
1381
|
+
xml_path: str | Path,
|
|
1382
|
+
xsd_path: Optional[str | Path] = None,
|
|
1383
|
+
xsd_search_strategy: Optional[
|
|
1384
|
+
Literal['by_namespace', 'by_file_name']
|
|
1385
|
+
] = None,
|
|
1386
|
+
base_url: Optional[str] = None,
|
|
1387
|
+
error_facets: Optional[ List[str] ] = None,
|
|
1388
|
+
pre_parse: Optional[bool] = True,
|
|
1389
|
+
write_to_csv: Optional[bool] = True,
|
|
1390
|
+
timestamped: Optional[bool] = True,
|
|
1391
|
+
reset_errors: bool = True,
|
|
1392
|
+
fail_on_errors: Optional[bool] = False
|
|
1393
|
+
) -> Tuple[
|
|
1394
|
+
List[ Dict[str, Any] ],
|
|
1395
|
+
str | None
|
|
1396
|
+
]:
|
|
1397
|
+
"""
|
|
1398
|
+
**Introduction**
|
|
1399
|
+
|
|
1400
|
+
Please make sure to have read the ``Introduction`` to the
|
|
1401
|
+
`keyword doc <https://github.com/MichaelHallik/robotframework-xmlvalidator/blob/main/docs/XmlValidator.html>`_.
|
|
1402
|
+
|
|
1403
|
+
That section contains information that is essential to the
|
|
1404
|
+
effective usage of the ``Validate Xml Files`` keyword and that
|
|
1405
|
+
will not be repeated here, to avoid redundancy.
|
|
1406
|
+
|
|
1407
|
+
This keyword supports *single file* validation, *batch* validation
|
|
1408
|
+
and *dynamic schema mapping*.
|
|
1409
|
+
|
|
1410
|
+
It also supports comprehensive and *configurable* error reporting
|
|
1411
|
+
and the *export* of all collected errors to CSV files.
|
|
1412
|
+
|
|
1413
|
+
**Basic use cases**
|
|
1414
|
+
|
|
1415
|
+
+---+----------------------------+------------------------+------------------------+------------------------------------------------------------+
|
|
1416
|
+
| | xsd_path passed with | xml_path points to | xsd_path points to | Keyword call result |
|
|
1417
|
+
+===+============================+========================+========================+============================================================+
|
|
1418
|
+
| 1 | Library Import | Single XML File | Single XSD File | XML file validated against preloaded schema. |
|
|
1419
|
+
+---+----------------------------+------------------------+------------------------+------------------------------------------------------------+
|
|
1420
|
+
| 2 | Library Import | Folder of XMLs | Single XSD File | Each XML in folder validated against preloaded schema. |
|
|
1421
|
+
+---+----------------------------+------------------------+------------------------+------------------------------------------------------------+
|
|
1422
|
+
| 4 | Keyword Call | Single XML File | Single XSD File | XML file validated against the passed schema. |
|
|
1423
|
+
+---+----------------------------+------------------------+------------------------+------------------------------------------------------------+
|
|
1424
|
+
| 5 | Keyword Call | Folder of XMLs | Single XSD File | Each XML in folder validated against the provided schema. |
|
|
1425
|
+
+---+----------------------------+------------------------+------------------------+------------------------------------------------------------+
|
|
1426
|
+
| 5 | Keyword Call | Single XML File | Folder of XSDs | Keyword attempts to find a matching XSD file, either by |
|
|
1427
|
+
| | | | | namespace or by filename. The latter is determined by arg |
|
|
1428
|
+
| | | | | ``xsd_search_strategy``. If a match is found, the XML file |
|
|
1429
|
+
| | | | | is validated against it. If no match is found, this fact |
|
|
1430
|
+
| | | | | is added to the error report. |
|
|
1431
|
+
+---+----------------------------+------------------------+------------------------+------------------------------------------------------------+
|
|
1432
|
+
| 6 | Keyword Call | Folder of XMLs | Folder of XSDs | The keyword attempts to match each XML to one of the |
|
|
1433
|
+
| | | | | schema files in the folder, either by namespace or by |
|
|
1434
|
+
| | | | | filename. The latter is determined by arg |
|
|
1435
|
+
| | | | | ``xsd_search_strategy``. For each XML that has a matching |
|
|
1436
|
+
| | | | | XSD, the XML is validated. Each XML without a matching XSD |
|
|
1437
|
+
| | | | | is added to the error report, with an appropriate 'reason'.|
|
|
1438
|
+
+---+----------------------------+------------------------+------------------------+------------------------------------------------------------+
|
|
1439
|
+
|
|
1440
|
+
**Error collecting and reporting**
|
|
1441
|
+
|
|
1442
|
+
Validation errors fall into three main categories:
|
|
1443
|
+
|
|
1444
|
+
- XSD violations:
|
|
1445
|
+
|
|
1446
|
+
- Captures cardinality issues, datatype mismatches, enumeration
|
|
1447
|
+
violations, pattern mismatches, namespace errors, etc.
|
|
1448
|
+
|
|
1449
|
+
- Malformed XML:
|
|
1450
|
+
|
|
1451
|
+
- Any syntax/parse issues.
|
|
1452
|
+
|
|
1453
|
+
- File-level issues:
|
|
1454
|
+
|
|
1455
|
+
- Detects empty, non-existent, or .
|
|
1456
|
+
- Uses sanity checks to validate syntax and type before XSD validation.
|
|
1457
|
+
|
|
1458
|
+
- Schema issues:
|
|
1459
|
+
|
|
1460
|
+
- Handles cases of invalid, unreadable, or unmatchable schema files.
|
|
1461
|
+
|
|
1462
|
+
All collected errors can optionally be written to a CSV file.
|
|
1463
|
+
Each row includes error details and the associated file name.
|
|
1464
|
+
|
|
1465
|
+
**Arguments**
|
|
1466
|
+
|
|
1467
|
+
``xml_path``
|
|
1468
|
+
|
|
1469
|
+
Path to an XML file or a directory containing `.xml` files.
|
|
1470
|
+
|
|
1471
|
+
``xsd_path``
|
|
1472
|
+
|
|
1473
|
+
Path to a single `.xsd` file or a directory containing one or more
|
|
1474
|
+
`.xsd` files. Required for dynamic schema resolution or schema
|
|
1475
|
+
overrides. Defaults to None.
|
|
1476
|
+
|
|
1477
|
+
``xsd_search_strategy``
|
|
1478
|
+
|
|
1479
|
+
Strategy for dynamic schema resolution when validating against
|
|
1480
|
+
multiple schemas. Required if `xsd_path` is a directory or not
|
|
1481
|
+
passed at all.
|
|
1482
|
+
|
|
1483
|
+
Defaults to 'by_namespace'.
|
|
1484
|
+
|
|
1485
|
+
``base_url``
|
|
1486
|
+
|
|
1487
|
+
Base directory for resolving schema imports and includes.
|
|
1488
|
+
Defaults to None.
|
|
1489
|
+
|
|
1490
|
+
``error_facets``
|
|
1491
|
+
|
|
1492
|
+
List of error details/attributes to include in the collecting
|
|
1493
|
+
and reporting of errors.
|
|
1494
|
+
|
|
1495
|
+
Defaults to ['path', 'reason'].
|
|
1496
|
+
|
|
1497
|
+
``pre_parse``
|
|
1498
|
+
|
|
1499
|
+
If True, performs well-formedness checks on all XML/XSD files
|
|
1500
|
+
before schema validation. Defaults to True.
|
|
1501
|
+
|
|
1502
|
+
``write_to_csv``
|
|
1503
|
+
|
|
1504
|
+
If True, writes all collected errors to a CSV file in the same
|
|
1505
|
+
folder as the validated XML(s). Defaults to True.
|
|
1506
|
+
|
|
1507
|
+
``timestamped``
|
|
1508
|
+
|
|
1509
|
+
Appends a timestamp to the CSV filename for uniqueness.
|
|
1510
|
+
Defaults to True.
|
|
1511
|
+
|
|
1512
|
+
``reset_errors``
|
|
1513
|
+
|
|
1514
|
+
Clears previously stored validation results before this run.
|
|
1515
|
+
Defaults to True.
|
|
1516
|
+
|
|
1517
|
+
``fail_on_errors``
|
|
1518
|
+
|
|
1519
|
+
Fails a test cases if, after checking the entire batch of one or
|
|
1520
|
+
XML files, one or more errors have been reported. Error
|
|
1521
|
+
reporting and exporting will not change.
|
|
1522
|
+
|
|
1523
|
+
**Returns**
|
|
1524
|
+
|
|
1525
|
+
A tuple, holding:
|
|
1526
|
+
|
|
1527
|
+
- A list of dictionaries:
|
|
1528
|
+
A list of all validation errors found during the run. Each
|
|
1529
|
+
error is a dictionary with items that are based on the
|
|
1530
|
+
`error_facets`.
|
|
1531
|
+
- A string or None:
|
|
1532
|
+
The path to the generated CSV file if there are errors and
|
|
1533
|
+
`write_to_csv=True`; otherwise None.
|
|
1534
|
+
|
|
1535
|
+
**Raises**
|
|
1536
|
+
|
|
1537
|
+
Since the keyword's purpose is to catch and collect various
|
|
1538
|
+
types of errors, for these errors no exceptions will be raised.
|
|
1539
|
+
|
|
1540
|
+
However, in certain situations the keyword may raise certain
|
|
1541
|
+
exceptions. For instance:
|
|
1542
|
+
|
|
1543
|
+
``FileNotFoundError``
|
|
1544
|
+
|
|
1545
|
+
For instance if the passed ``xml_path`` is non-existing, points
|
|
1546
|
+
to a non-xml file or points to an empty folder.
|
|
1547
|
+
|
|
1548
|
+
``IOError``
|
|
1549
|
+
|
|
1550
|
+
If writing the CSV file fails due to filesystem restrictions.
|
|
1551
|
+
"""
|
|
1552
|
+
# Reset attributes, if requested.
|
|
1553
|
+
if reset_errors:
|
|
1554
|
+
self.validator_results.reset()
|
|
1555
|
+
# Determine and resolve/normalize the XML file path(s).
|
|
1556
|
+
xml_file_paths, is_single_xml_file = (
|
|
1557
|
+
self.validator_utils.get_file_paths(
|
|
1558
|
+
xml_path, 'xml'
|
|
1559
|
+
)
|
|
1560
|
+
)
|
|
1561
|
+
# Pair each XML file with it's proper XSD counterpart.
|
|
1562
|
+
validations = self._determine_validations(
|
|
1563
|
+
xml_file_paths,
|
|
1564
|
+
xsd_path=xsd_path,
|
|
1565
|
+
xsd_search_strategy=xsd_search_strategy,
|
|
1566
|
+
base_url=base_url
|
|
1567
|
+
)
|
|
1568
|
+
# Validate each XML file with the corresponding schema.
|
|
1569
|
+
for xml_file_path, xsd_file_path in validations.items():
|
|
1570
|
+
# The actual validation.
|
|
1571
|
+
is_valid, errors = self._validate_xml(
|
|
1572
|
+
xml_file_path,
|
|
1573
|
+
xsd_file_path=xsd_file_path,
|
|
1574
|
+
base_url=base_url,
|
|
1575
|
+
error_facets=error_facets,
|
|
1576
|
+
pre_parse=pre_parse
|
|
1577
|
+
)
|
|
1578
|
+
# Process the validation results.
|
|
1579
|
+
if is_valid:
|
|
1580
|
+
self.validator_results.add_valid_file(xml_file_path)
|
|
1581
|
+
else:
|
|
1582
|
+
self.validator_results.add_invalid_file(xml_file_path)
|
|
1583
|
+
self.validator_results.add_file_errors(xml_file_path, errors)
|
|
1584
|
+
self.validator_results.log_file_errors(errors) # type: ignore
|
|
1585
|
+
# Write errors to a single CSV file if requested.
|
|
1586
|
+
if write_to_csv and self.validator_results.errors_by_file:
|
|
1587
|
+
csv_path = self.validator_results.write_errors_to_csv(
|
|
1588
|
+
self.validator_results.errors_by_file,
|
|
1589
|
+
xml_file_paths[0].parent
|
|
1590
|
+
if is_single_xml_file else xml_file_paths[0],
|
|
1591
|
+
include_timestamp=timestamped,
|
|
1592
|
+
file_name_column="file_name"
|
|
1593
|
+
)
|
|
1594
|
+
else:
|
|
1595
|
+
csv_path = None
|
|
1596
|
+
# Log a summary of the test run.
|
|
1597
|
+
self.validator_results.log_summary()
|
|
1598
|
+
if fail_on_errors and self.validator_results.errors_by_file:
|
|
1599
|
+
raise Failure(
|
|
1600
|
+
f"{len(self.validator_results.errors_by_file)} errors have been detected."
|
|
1601
|
+
)
|
|
1602
|
+
return (
|
|
1603
|
+
self.validator_results.errors_by_file,
|
|
1604
|
+
csv_path if csv_path else None
|
|
1605
|
+
)
|