xmlgenerator 0.2.1__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {xmlgenerator-0.2.1/xmlgenerator.egg-info → xmlgenerator-0.4.0}/PKG-INFO +6 -3
- {xmlgenerator-0.2.1 → xmlgenerator-0.4.0}/README.md +5 -2
- {xmlgenerator-0.2.1 → xmlgenerator-0.4.0}/setup.py +1 -1
- {xmlgenerator-0.2.1 → xmlgenerator-0.4.0}/xmlgenerator/arguments.py +7 -1
- {xmlgenerator-0.2.1 → xmlgenerator-0.4.0}/xmlgenerator/bootstrap.py +6 -4
- {xmlgenerator-0.2.1 → xmlgenerator-0.4.0}/xmlgenerator/configuration.py +2 -1
- xmlgenerator-0.4.0/xmlgenerator/generator.py +480 -0
- xmlgenerator-0.4.0/xmlgenerator/randomization.py +135 -0
- {xmlgenerator-0.2.1 → xmlgenerator-0.4.0}/xmlgenerator/substitution.py +49 -52
- {xmlgenerator-0.2.1 → xmlgenerator-0.4.0/xmlgenerator.egg-info}/PKG-INFO +6 -3
- xmlgenerator-0.2.1/xmlgenerator/generator.py +0 -412
- xmlgenerator-0.2.1/xmlgenerator/randomization.py +0 -76
- {xmlgenerator-0.2.1 → xmlgenerator-0.4.0}/LICENSE +0 -0
- {xmlgenerator-0.2.1 → xmlgenerator-0.4.0}/setup.cfg +0 -0
- {xmlgenerator-0.2.1 → xmlgenerator-0.4.0}/xmlgenerator/__init__.py +0 -0
- {xmlgenerator-0.2.1 → xmlgenerator-0.4.0}/xmlgenerator/validation.py +0 -0
- {xmlgenerator-0.2.1 → xmlgenerator-0.4.0}/xmlgenerator.egg-info/SOURCES.txt +0 -0
- {xmlgenerator-0.2.1 → xmlgenerator-0.4.0}/xmlgenerator.egg-info/dependency_links.txt +0 -0
- {xmlgenerator-0.2.1 → xmlgenerator-0.4.0}/xmlgenerator.egg-info/entry_points.txt +0 -0
- {xmlgenerator-0.2.1 → xmlgenerator-0.4.0}/xmlgenerator.egg-info/requires.txt +0 -0
- {xmlgenerator-0.2.1 → xmlgenerator-0.4.0}/xmlgenerator.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: xmlgenerator
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.4.0
|
4
4
|
Summary: Generates XML documents from XSD schemas
|
5
5
|
Home-page: https://github.com/lexakimov/xmlgenerator
|
6
6
|
Author: Alexey Akimov
|
@@ -136,12 +136,13 @@ positional arguments:
|
|
136
136
|
options:
|
137
137
|
-h, --help show this help message and exit
|
138
138
|
-c, --config <config.yml> pass yaml configuration file
|
139
|
+
-l, --locale <locale> randomizer locale (default: en_US)
|
139
140
|
-o, --output <output.xml> save output to dir or file
|
140
141
|
-p, --pretty prettify output XML
|
141
142
|
-v, --validation <validation> validate generated XML document (none, schema, schematron, default is schema)
|
142
143
|
-ff, --fail-fast terminate execution on validation error (default is true)
|
143
144
|
-e, --encoding <encoding> output XML encoding (utf-8, windows-1251, default is utf-8)
|
144
|
-
|
145
|
+
-s, --seed <seed> set randomization seed
|
145
146
|
-d, --debug enable debug mode
|
146
147
|
-V, --version shows current version
|
147
148
|
-C, --completion <shell> print shell completion script (bash, zsh, tcsh)
|
@@ -164,7 +165,7 @@ global:
|
|
164
165
|
source_filename: ...
|
165
166
|
|
166
167
|
# Filename template for saving the generated document.
|
167
|
-
# Default value: `{{
|
168
|
+
# Default value: `{{ source_extracted }}_{{ uuid }}` (xsd schema filename + random UUID)
|
168
169
|
output_filename: ...
|
169
170
|
|
170
171
|
# Random value generator settings
|
@@ -172,6 +173,8 @@ global:
|
|
172
173
|
# Probability of adding optional elements (0.0-1.0)
|
173
174
|
# Default value: 0.5
|
174
175
|
probability: 1
|
176
|
+
# Limit for the minimal number of elements
|
177
|
+
min_occurs: 0
|
175
178
|
# Limit for the maximum number of elements
|
176
179
|
max_occurs: 5
|
177
180
|
# Minimum string length
|
@@ -107,12 +107,13 @@ positional arguments:
|
|
107
107
|
options:
|
108
108
|
-h, --help show this help message and exit
|
109
109
|
-c, --config <config.yml> pass yaml configuration file
|
110
|
+
-l, --locale <locale> randomizer locale (default: en_US)
|
110
111
|
-o, --output <output.xml> save output to dir or file
|
111
112
|
-p, --pretty prettify output XML
|
112
113
|
-v, --validation <validation> validate generated XML document (none, schema, schematron, default is schema)
|
113
114
|
-ff, --fail-fast terminate execution on validation error (default is true)
|
114
115
|
-e, --encoding <encoding> output XML encoding (utf-8, windows-1251, default is utf-8)
|
115
|
-
|
116
|
+
-s, --seed <seed> set randomization seed
|
116
117
|
-d, --debug enable debug mode
|
117
118
|
-V, --version shows current version
|
118
119
|
-C, --completion <shell> print shell completion script (bash, zsh, tcsh)
|
@@ -135,7 +136,7 @@ global:
|
|
135
136
|
source_filename: ...
|
136
137
|
|
137
138
|
# Filename template for saving the generated document.
|
138
|
-
# Default value: `{{
|
139
|
+
# Default value: `{{ source_extracted }}_{{ uuid }}` (xsd schema filename + random UUID)
|
139
140
|
output_filename: ...
|
140
141
|
|
141
142
|
# Random value generator settings
|
@@ -143,6 +144,8 @@ global:
|
|
143
144
|
# Probability of adding optional elements (0.0-1.0)
|
144
145
|
# Default value: 0.5
|
145
146
|
probability: 1
|
147
|
+
# Limit for the minimal number of elements
|
148
|
+
min_occurs: 0
|
146
149
|
# Limit for the maximum number of elements
|
147
150
|
max_occurs: 5
|
148
151
|
# Minimum string length
|
@@ -39,6 +39,12 @@ def _get_parser():
|
|
39
39
|
dest="config_yaml",
|
40
40
|
help="pass yaml configuration file"
|
41
41
|
)
|
42
|
+
parser.add_argument(
|
43
|
+
"-l", "--locale",
|
44
|
+
metavar="<locale>",
|
45
|
+
default="en_US",
|
46
|
+
help="randomizer locale (default: %(default)s)"
|
47
|
+
)
|
42
48
|
output_arg = parser.add_argument(
|
43
49
|
"-o", "--output",
|
44
50
|
metavar="<output.xml>",
|
@@ -71,7 +77,7 @@ def _get_parser():
|
|
71
77
|
help="output XML encoding (utf-8, windows-1251, default is utf-8)"
|
72
78
|
)
|
73
79
|
parser.add_argument(
|
74
|
-
"--seed",
|
80
|
+
"-s", "--seed",
|
75
81
|
metavar="<seed>",
|
76
82
|
help="set randomization seed"
|
77
83
|
)
|
@@ -42,14 +42,15 @@ def _main():
|
|
42
42
|
|
43
43
|
config = load_config(args.config_yaml)
|
44
44
|
|
45
|
-
randomizer = Randomizer(args.seed)
|
45
|
+
randomizer = Randomizer(args.seed, args.locale)
|
46
46
|
substitutor = Substitutor(randomizer)
|
47
47
|
generator = XmlGenerator(randomizer, substitutor)
|
48
48
|
validator = XmlValidator(args.validation, args.fail_fast)
|
49
49
|
|
50
|
-
|
51
|
-
|
52
|
-
|
50
|
+
total_count = len(xsd_files)
|
51
|
+
logger.debug('found %s schemas', total_count)
|
52
|
+
for index, xsd_file in enumerate(xsd_files):
|
53
|
+
logger.info('processing schema %s of %s: %s', index + 1, total_count, xsd_file.name)
|
53
54
|
|
54
55
|
# get configuration override for current schema
|
55
56
|
local_config = config.get_for_file(xsd_file.name)
|
@@ -88,6 +89,7 @@ def _main():
|
|
88
89
|
|
89
90
|
|
90
91
|
def _setup_loggers(args):
|
92
|
+
logging.addLevelName(logging.WARNING, 'WARN')
|
91
93
|
log_level = logging.DEBUG if args.debug else logging.INFO
|
92
94
|
logger.setLevel(log_level)
|
93
95
|
configuration.logger.setLevel(log_level)
|
@@ -13,6 +13,7 @@ logger = logging.getLogger(__name__)
|
|
13
13
|
@dataclass
|
14
14
|
class RandomizationConfig:
|
15
15
|
probability: float = field(default=None)
|
16
|
+
min_occurs: int = field(default=None)
|
16
17
|
max_occurs: int = field(default=None)
|
17
18
|
min_length: int = field(default=None)
|
18
19
|
max_length: int = field(default=None)
|
@@ -36,7 +37,7 @@ class GeneratorConfig:
|
|
36
37
|
@dataclass
|
37
38
|
class GlobalGeneratorConfig(GeneratorConfig):
|
38
39
|
source_filename: str = field(default='(?P<extracted>.*).(xsd|XSD)')
|
39
|
-
output_filename: str = field(default='{{
|
40
|
+
output_filename: str = field(default='{{ source_extracted }}_{{ uuid }}')
|
40
41
|
randomization: GlobalRandomizationConfig = field(default_factory=lambda: GlobalRandomizationConfig())
|
41
42
|
|
42
43
|
|
@@ -0,0 +1,480 @@
|
|
1
|
+
import logging
|
2
|
+
from decimal import Decimal
|
3
|
+
|
4
|
+
from lxml import etree
|
5
|
+
from xmlschema.validators import XsdComplexType, XsdAtomicRestriction, XsdTotalDigitsFacet, XsdElement, \
|
6
|
+
XsdGroup, XsdFractionDigitsFacet, XsdLengthFacet, XsdMaxLengthFacet, XsdMinExclusiveFacet, XsdMinInclusiveFacet, \
|
7
|
+
XsdMinLengthFacet, XsdAnyElement, XsdAtomicBuiltin, XsdEnumerationFacets, XsdMaxExclusiveFacet, XsdMaxInclusiveFacet
|
8
|
+
|
9
|
+
from xmlgenerator.configuration import GeneratorConfig
|
10
|
+
from xmlgenerator.randomization import Randomizer
|
11
|
+
from xmlgenerator.substitution import Substitutor
|
12
|
+
|
13
|
+
logger = logging.getLogger(__name__)
|
14
|
+
|
15
|
+
|
16
|
+
class XmlGenerator:
|
17
|
+
def __init__(self, randomizer: Randomizer, substitutor: Substitutor):
|
18
|
+
self.randomizer = randomizer
|
19
|
+
self.substitutor = substitutor
|
20
|
+
|
21
|
+
def generate_xml(self, xsd_schema, local_config: GeneratorConfig) -> etree.Element:
|
22
|
+
logger.debug('generate xml document...')
|
23
|
+
ns_map = {None if k == '' else k: v for k, v in xsd_schema.namespaces.items() if v != ''}
|
24
|
+
xsd_root_element = xsd_schema.root_elements[0]
|
25
|
+
xml_root_element = etree.Element(xsd_root_element.name, nsmap=ns_map)
|
26
|
+
xml_tree = etree.ElementTree(xml_root_element)
|
27
|
+
self._add_elements(xml_tree, xml_root_element, xsd_root_element, local_config)
|
28
|
+
return xml_root_element
|
29
|
+
|
30
|
+
def _add_elements(self, xml_tree, xml_element, xsd_element, local_config: GeneratorConfig) -> None:
|
31
|
+
rand_config = local_config.randomization
|
32
|
+
min_occurs_conf = rand_config.min_occurs
|
33
|
+
max_occurs_conf = rand_config.max_occurs
|
34
|
+
|
35
|
+
# Process child elements --------------------------------------------------------------------------------------
|
36
|
+
if isinstance(xsd_element, XsdElement):
|
37
|
+
element_xpath = xml_tree.getpath(xml_element)
|
38
|
+
logger.debug('element: %s [created]', element_xpath)
|
39
|
+
|
40
|
+
xsd_element_type = getattr(xsd_element, 'type', None)
|
41
|
+
|
42
|
+
# Add attributes if they are
|
43
|
+
attributes = getattr(xsd_element, 'attributes', dict())
|
44
|
+
if len(attributes) > 0 and xsd_element_type.local_name != 'anyType':
|
45
|
+
for attr_name, attr in attributes.items():
|
46
|
+
logger.debug('element: %s; attribute: "%s" - [processing]', element_xpath, attr_name)
|
47
|
+
use = attr.use # optional | required | prohibited
|
48
|
+
if use == 'prohibited':
|
49
|
+
logger.debug('element: %s; attribute: "%s" - [skipped]', element_xpath, attr_name)
|
50
|
+
continue
|
51
|
+
elif use == 'optional':
|
52
|
+
if self.randomizer.random() > rand_config.probability:
|
53
|
+
logger.debug('element: %s; attribute: "%s" - [skipped]', element_xpath, attr_name)
|
54
|
+
continue
|
55
|
+
|
56
|
+
attr_value = self._generate_value(attr.type, attr_name, local_config)
|
57
|
+
if attr_value is not None:
|
58
|
+
xml_element.set(attr_name, str(attr_value))
|
59
|
+
logger.debug('element: %s; attribute: "%s" = "%s"', element_xpath, attr_name, attr_value)
|
60
|
+
|
61
|
+
if isinstance(xsd_element_type, XsdAtomicBuiltin):
|
62
|
+
text = self._generate_value(xsd_element_type, xsd_element.name, local_config)
|
63
|
+
xml_element.text = text
|
64
|
+
logger.debug('element: %s = "%s"', element_xpath, text)
|
65
|
+
|
66
|
+
elif isinstance(xsd_element_type, XsdAtomicRestriction):
|
67
|
+
text = self._generate_value(xsd_element_type, xsd_element.name, local_config)
|
68
|
+
xml_element.text = text
|
69
|
+
logger.debug('element: %s = "%s"', element_xpath, text)
|
70
|
+
|
71
|
+
elif isinstance(xsd_element_type, XsdComplexType):
|
72
|
+
xsd_element_type_content = xsd_element_type.content
|
73
|
+
if isinstance(xsd_element_type_content, XsdGroup):
|
74
|
+
self._add_elements(xml_tree, xml_element, xsd_element_type_content, local_config)
|
75
|
+
else:
|
76
|
+
raise RuntimeError()
|
77
|
+
|
78
|
+
else:
|
79
|
+
raise RuntimeError()
|
80
|
+
|
81
|
+
elif isinstance(xsd_element, XsdGroup):
|
82
|
+
model = xsd_element.model
|
83
|
+
|
84
|
+
min_occurs = getattr(xsd_element, 'min_occurs', None)
|
85
|
+
max_occurs = getattr(xsd_element, 'max_occurs', None)
|
86
|
+
min_occurs, max_occurs = merge_constraints(
|
87
|
+
schema_min=min_occurs,
|
88
|
+
schema_max=max_occurs,
|
89
|
+
config_min=min_occurs_conf,
|
90
|
+
config_max=max_occurs_conf
|
91
|
+
)
|
92
|
+
if max_occurs is None:
|
93
|
+
max_occurs = 10
|
94
|
+
group_occurs = self.randomizer.integer(min_occurs, max_occurs)
|
95
|
+
logger.debug('add %s (random between %s and %s) groups of type "%s"',
|
96
|
+
group_occurs, min_occurs, max_occurs, model)
|
97
|
+
|
98
|
+
if model == 'all':
|
99
|
+
for _ in range(group_occurs):
|
100
|
+
xsd_group_content = xsd_element.content
|
101
|
+
for xsd_child_element_type in xsd_group_content:
|
102
|
+
|
103
|
+
min_occurs = getattr(xsd_child_element_type, 'min_occurs', None)
|
104
|
+
max_occurs = getattr(xsd_child_element_type, 'max_occurs', None)
|
105
|
+
min_occurs, max_occurs = merge_constraints(
|
106
|
+
schema_min=min_occurs,
|
107
|
+
schema_max=max_occurs,
|
108
|
+
config_min=min_occurs_conf,
|
109
|
+
config_max=max_occurs_conf
|
110
|
+
)
|
111
|
+
if max_occurs is None:
|
112
|
+
max_occurs = 10
|
113
|
+
element_occurs = self.randomizer.integer(min_occurs, max_occurs)
|
114
|
+
logger.debug('element_occurs: %s (random between %s and %s)', element_occurs, min_occurs,
|
115
|
+
max_occurs)
|
116
|
+
|
117
|
+
for _ in range(element_occurs):
|
118
|
+
xml_child_element = etree.SubElement(xml_element, xsd_child_element_type.name)
|
119
|
+
self._add_elements(xml_tree, xml_child_element, xsd_child_element_type, local_config)
|
120
|
+
|
121
|
+
elif model == 'sequence':
|
122
|
+
for _ in range(group_occurs):
|
123
|
+
xsd_group_content = xsd_element.content
|
124
|
+
for xsd_child_element_type in xsd_group_content:
|
125
|
+
if isinstance(xsd_child_element_type, XsdElement):
|
126
|
+
|
127
|
+
min_occurs = getattr(xsd_child_element_type, 'min_occurs', None)
|
128
|
+
max_occurs = getattr(xsd_child_element_type, 'max_occurs', None)
|
129
|
+
min_occurs, max_occurs = merge_constraints(
|
130
|
+
schema_min=min_occurs,
|
131
|
+
schema_max=max_occurs,
|
132
|
+
config_min=min_occurs_conf,
|
133
|
+
config_max=max_occurs_conf
|
134
|
+
)
|
135
|
+
if max_occurs is None:
|
136
|
+
max_occurs = 10
|
137
|
+
element_occurs = self.randomizer.integer(min_occurs, max_occurs)
|
138
|
+
logger.debug('element_occurs: %s (random between %s and %s)', element_occurs, min_occurs,
|
139
|
+
max_occurs)
|
140
|
+
|
141
|
+
for _ in range(element_occurs):
|
142
|
+
xml_child_element = etree.SubElement(xml_element, xsd_child_element_type.name)
|
143
|
+
self._add_elements(xml_tree, xml_child_element, xsd_child_element_type, local_config)
|
144
|
+
|
145
|
+
elif isinstance(xsd_child_element_type, XsdGroup):
|
146
|
+
xml_child_element = xml_element
|
147
|
+
self._add_elements(xml_tree, xml_child_element, xsd_child_element_type, local_config)
|
148
|
+
|
149
|
+
elif isinstance(xsd_child_element_type, XsdAnyElement):
|
150
|
+
xml_child_element = etree.SubElement(xml_element, "Any")
|
151
|
+
self._add_elements(xml_tree, xml_child_element, xsd_child_element_type, local_config)
|
152
|
+
|
153
|
+
else:
|
154
|
+
raise RuntimeError(xsd_child_element_type)
|
155
|
+
|
156
|
+
elif model == 'choice':
|
157
|
+
for _ in range(group_occurs):
|
158
|
+
xsd_child_element_type = self.randomizer.any(xsd_element)
|
159
|
+
|
160
|
+
min_occurs = getattr(xsd_child_element_type, 'min_occurs', None)
|
161
|
+
max_occurs = getattr(xsd_child_element_type, 'max_occurs', None)
|
162
|
+
min_occurs, max_occurs = merge_constraints(
|
163
|
+
schema_min=min_occurs,
|
164
|
+
schema_max=max_occurs,
|
165
|
+
config_min=min_occurs_conf,
|
166
|
+
config_max=max_occurs_conf
|
167
|
+
)
|
168
|
+
if max_occurs is None:
|
169
|
+
max_occurs = 10
|
170
|
+
element_occurs = self.randomizer.integer(min_occurs, max_occurs)
|
171
|
+
logger.debug('element_occurs: %s (random between %s and %s)', element_occurs, min_occurs,
|
172
|
+
max_occurs)
|
173
|
+
|
174
|
+
for _ in range(element_occurs):
|
175
|
+
xml_child_element = etree.SubElement(xml_element, xsd_child_element_type.name)
|
176
|
+
self._add_elements(xml_tree, xml_child_element, xsd_child_element_type, local_config)
|
177
|
+
|
178
|
+
else:
|
179
|
+
raise RuntimeError()
|
180
|
+
|
181
|
+
elif isinstance(xsd_element, XsdAnyElement):
|
182
|
+
# для any не добавляем никаких дочерних тегов и атрибутов
|
183
|
+
pass
|
184
|
+
|
185
|
+
else:
|
186
|
+
raise RuntimeError()
|
187
|
+
|
188
|
+
def _generate_value(self, xsd_type, target_name, local_config: GeneratorConfig) -> str | None:
|
189
|
+
if xsd_type is None:
|
190
|
+
raise RuntimeError(f"xsd_type is None. Target name: {target_name}")
|
191
|
+
|
192
|
+
if isinstance(xsd_type, XsdComplexType):
|
193
|
+
return None
|
194
|
+
|
195
|
+
# -------------------------------------------------------------------------------------------------------------
|
196
|
+
# Ищем переопределение значения в конфигурации
|
197
|
+
value_override = local_config.value_override
|
198
|
+
is_found, overridden_value = self.substitutor.substitute_value(target_name, value_override.items())
|
199
|
+
if is_found:
|
200
|
+
logger.debug('value resolved: "%s"', overridden_value)
|
201
|
+
return overridden_value
|
202
|
+
|
203
|
+
# -------------------------------------------------------------------------------------------------------------
|
204
|
+
# If there is an enumeration, select a random value from it
|
205
|
+
enumeration = getattr(xsd_type, 'enumeration', None)
|
206
|
+
if enumeration is not None:
|
207
|
+
random_enum = self.randomizer.any(enumeration)
|
208
|
+
logger.debug('use random value from enumeration: "%s" %s', random_enum, enumeration)
|
209
|
+
return str(random_enum)
|
210
|
+
|
211
|
+
# -------------------------------------------------------------------------------------------------------------
|
212
|
+
# Генерируем значения для стандартных типов и типов с ограничениями
|
213
|
+
if isinstance(xsd_type, XsdAtomicBuiltin) or isinstance(xsd_type, XsdAtomicRestriction):
|
214
|
+
# Выясняем ограничения
|
215
|
+
min_length = getattr(xsd_type, 'min_length', None) # None | int
|
216
|
+
max_length = getattr(xsd_type, 'max_length', None) # None | int
|
217
|
+
|
218
|
+
min_value = getattr(xsd_type, 'min_value', None) # None | int
|
219
|
+
max_value = getattr(xsd_type, 'max_value', None) # None
|
220
|
+
|
221
|
+
total_digits = None
|
222
|
+
fraction_digits = None
|
223
|
+
patterns = getattr(xsd_type, 'patterns', None)
|
224
|
+
|
225
|
+
validators = getattr(xsd_type, 'validators', None)
|
226
|
+
for validator in validators:
|
227
|
+
if isinstance(validator, XsdMinExclusiveFacet):
|
228
|
+
min_value = validator.value
|
229
|
+
elif isinstance(validator, XsdMinInclusiveFacet):
|
230
|
+
min_value = validator.value
|
231
|
+
elif isinstance(validator, XsdMaxExclusiveFacet):
|
232
|
+
max_value = validator.value
|
233
|
+
elif isinstance(validator, XsdMaxInclusiveFacet):
|
234
|
+
max_value = validator.value
|
235
|
+
elif isinstance(validator, XsdLengthFacet):
|
236
|
+
min_length = validator.value
|
237
|
+
max_length = validator.value
|
238
|
+
elif isinstance(validator, XsdMinLengthFacet):
|
239
|
+
min_length = validator.value
|
240
|
+
elif isinstance(validator, XsdMaxLengthFacet):
|
241
|
+
max_length = validator.value
|
242
|
+
elif isinstance(validator, XsdTotalDigitsFacet):
|
243
|
+
total_digits = validator.value
|
244
|
+
elif isinstance(validator, XsdFractionDigitsFacet):
|
245
|
+
fraction_digits = validator.value
|
246
|
+
elif isinstance(validator, XsdEnumerationFacets):
|
247
|
+
pass
|
248
|
+
elif callable(validator):
|
249
|
+
pass
|
250
|
+
else:
|
251
|
+
raise RuntimeError(f"Unhandled validator: {validator}")
|
252
|
+
|
253
|
+
if isinstance(min_value, Decimal):
|
254
|
+
min_value = float(min_value)
|
255
|
+
if isinstance(max_value, Decimal):
|
256
|
+
max_value = float(max_value)
|
257
|
+
|
258
|
+
rand_config = local_config.randomization
|
259
|
+
|
260
|
+
logger.debug('bounds before adjust: min_length: %4s; max_length: %4s', min_length, max_length)
|
261
|
+
min_length, max_length = merge_constraints(
|
262
|
+
schema_min=min_length,
|
263
|
+
schema_max=max_length,
|
264
|
+
config_min=rand_config.min_length,
|
265
|
+
config_max=rand_config.max_length
|
266
|
+
)
|
267
|
+
logger.debug('bounds after adjust: min_length: %4s; max_length: %4s', min_length, max_length)
|
268
|
+
|
269
|
+
type_id = xsd_type.id or xsd_type.base_type.id or xsd_type.root_type.id
|
270
|
+
logger.debug('generate value for type: "%s"', type_id)
|
271
|
+
|
272
|
+
match type_id:
|
273
|
+
case 'boolean':
|
274
|
+
result = self._generate_boolean()
|
275
|
+
case 'string':
|
276
|
+
result = self._generate_string(min_length, max_length, patterns)
|
277
|
+
case 'integer':
|
278
|
+
result = self._generate_integer(min_value, max_value, total_digits)
|
279
|
+
case 'decimal':
|
280
|
+
result = self._generate_decimal(rand_config, min_value, max_value, total_digits, fraction_digits)
|
281
|
+
case 'float':
|
282
|
+
result = self._generate_float(rand_config, min_value, max_value)
|
283
|
+
case 'double':
|
284
|
+
result = self._generate_double(rand_config, min_value, max_value)
|
285
|
+
case 'duration':
|
286
|
+
result = self._generate_duration()
|
287
|
+
case 'dateTime':
|
288
|
+
result = self._generate_datetime()
|
289
|
+
case 'date':
|
290
|
+
result = self._generate_date()
|
291
|
+
case 'time':
|
292
|
+
result = self._generate_time()
|
293
|
+
case 'gYearMonth':
|
294
|
+
result = self._generate_gyearmonth()
|
295
|
+
case 'gYear':
|
296
|
+
result = self._generate_gyear()
|
297
|
+
case 'gMonthDay':
|
298
|
+
result = self._generate_gmonthday()
|
299
|
+
case 'gDay':
|
300
|
+
result = self._generate_gday()
|
301
|
+
case 'gMonth':
|
302
|
+
result = self._generate_gmonth()
|
303
|
+
case 'hexBinary':
|
304
|
+
result = self._generate_hex_binary()
|
305
|
+
case 'base64Binary':
|
306
|
+
result = self._generate_base64_binary()
|
307
|
+
case 'anyURI':
|
308
|
+
result = self._generate_any_uri()
|
309
|
+
case 'QName':
|
310
|
+
result = self._generate_qname()
|
311
|
+
case 'NOTATION':
|
312
|
+
result = self._generate_notation()
|
313
|
+
case _:
|
314
|
+
raise RuntimeError(type_id)
|
315
|
+
generated_value = result
|
316
|
+
logger.debug('value generated: "%s"', generated_value)
|
317
|
+
return generated_value
|
318
|
+
|
319
|
+
# -------------------------------------------------------------------------------------------------------------
|
320
|
+
# Проверяем базовый тип
|
321
|
+
base_type = getattr(xsd_type, 'base_type', None)
|
322
|
+
|
323
|
+
# невозможный кейс (только если попался комплексный тип)
|
324
|
+
if base_type is None:
|
325
|
+
raise RuntimeError(f"base_type is None. Target name: {target_name}")
|
326
|
+
|
327
|
+
raise RuntimeError(f"Can't generate value - unhandled type. Target name: {target_name}")
|
328
|
+
|
329
|
+
def _generate_boolean(self):
|
330
|
+
return self.randomizer.any(['true', 'false'])
|
331
|
+
|
332
|
+
def _generate_string(self, min_length, max_length, patterns):
|
333
|
+
if patterns is not None:
|
334
|
+
# Генерация строки по regex
|
335
|
+
random_enum = self.randomizer.any(patterns)
|
336
|
+
random_pattern = random_enum.attrib['value']
|
337
|
+
return self.randomizer.regex(random_pattern)
|
338
|
+
|
339
|
+
# Иначе генерируем случайную строку
|
340
|
+
return self.randomizer.ascii_string(min_length, max_length)
|
341
|
+
|
342
|
+
def _generate_integer(self, min_value, max_value, total_digits):
|
343
|
+
if total_digits:
|
344
|
+
min_value = 10 ** (total_digits - 1)
|
345
|
+
max_value = (10 ** total_digits) - 1
|
346
|
+
rnd_int = self.randomizer.integer(min_value, max_value)
|
347
|
+
return str(rnd_int)
|
348
|
+
|
349
|
+
def _generate_decimal(self, rand_config, schema_min, schema_max, total_digits, fraction_digits):
|
350
|
+
if fraction_digits is None:
|
351
|
+
fraction_digits = self.randomizer.integer(1, 3)
|
352
|
+
|
353
|
+
if fraction_digits > 4:
|
354
|
+
fraction_digits = self.randomizer.integer(1, 4)
|
355
|
+
|
356
|
+
if total_digits is None:
|
357
|
+
total_digits = 10 + fraction_digits
|
358
|
+
|
359
|
+
if total_digits > 10:
|
360
|
+
total_digits = self.randomizer.integer(6, total_digits - 2)
|
361
|
+
|
362
|
+
integer_digits = total_digits - fraction_digits
|
363
|
+
|
364
|
+
# negative bound
|
365
|
+
digit_min = -(10 ** integer_digits - 1)
|
366
|
+
# positive bound
|
367
|
+
digit_max = 10 ** integer_digits - 1
|
368
|
+
logger.debug("integer digits: %s; digit_min: %s; digit_max: %s", integer_digits, digit_min, digit_max)
|
369
|
+
|
370
|
+
logger.debug('bounds before adjust: min_value: %4s; max_value: %4s', schema_min, schema_max)
|
371
|
+
config_min = rand_config.min_inclusive
|
372
|
+
config_max = rand_config.max_inclusive
|
373
|
+
effective_min, effective_max \
|
374
|
+
= merge_constraints(digit_min, digit_max, schema_min, schema_max, config_min, config_max)
|
375
|
+
logger.debug('bounds after adjust: min_value: %4s; max_value: %4s', effective_min, effective_max)
|
376
|
+
|
377
|
+
random_float = self.randomizer.float(effective_min, effective_max)
|
378
|
+
return f"{random_float:.{fraction_digits}f}"
|
379
|
+
|
380
|
+
def _generate_float(self, rand_config, min_value, max_value):
|
381
|
+
return self._generate_decimal(rand_config, min_value, max_value, None, 2)
|
382
|
+
|
383
|
+
def _generate_double(self, rand_config, min_value, max_value):
|
384
|
+
return self._generate_decimal(rand_config, min_value, max_value, None, 2)
|
385
|
+
|
386
|
+
def _generate_duration(self):
|
387
|
+
raise RuntimeError("not yet implemented")
|
388
|
+
|
389
|
+
def _generate_datetime(self):
|
390
|
+
random_datetime = self.randomizer.random_datetime()
|
391
|
+
formatted = random_datetime.isoformat()
|
392
|
+
return formatted
|
393
|
+
|
394
|
+
def _generate_date(self):
|
395
|
+
random_date = self.randomizer.random_date()
|
396
|
+
formatted = random_date.isoformat()
|
397
|
+
return formatted
|
398
|
+
|
399
|
+
def _generate_time(self):
|
400
|
+
random_time = self.randomizer.random_time()
|
401
|
+
formatted = random_time.isoformat()
|
402
|
+
return formatted
|
403
|
+
|
404
|
+
def _generate_gyearmonth(self):
|
405
|
+
random_date = self.randomizer.random_date()
|
406
|
+
formatted = random_date.strftime('%Y-%m')
|
407
|
+
return formatted
|
408
|
+
|
409
|
+
def _generate_gyear(self):
|
410
|
+
return str(self.randomizer.integer(2000, 2050))
|
411
|
+
|
412
|
+
def _generate_gmonthday(self):
|
413
|
+
random_date = self.randomizer.random_date()
|
414
|
+
formatted = random_date.strftime('--%m-%d')
|
415
|
+
return formatted
|
416
|
+
|
417
|
+
def _generate_gday(self):
|
418
|
+
random_date = self.randomizer.random_date()
|
419
|
+
formatted = random_date.strftime('---%d')
|
420
|
+
return formatted
|
421
|
+
|
422
|
+
def _generate_gmonth(self):
|
423
|
+
random_date = self.randomizer.random_date()
|
424
|
+
formatted = random_date.strftime('--%m--')
|
425
|
+
return formatted
|
426
|
+
|
427
|
+
def _generate_hex_binary(self):
|
428
|
+
raise RuntimeError("not yet implemented")
|
429
|
+
|
430
|
+
def _generate_base64_binary(self):
|
431
|
+
raise RuntimeError("not yet implemented")
|
432
|
+
|
433
|
+
def _generate_any_uri(self):
|
434
|
+
raise RuntimeError("not yet implemented")
|
435
|
+
|
436
|
+
def _generate_qname(self):
|
437
|
+
raise RuntimeError("not yet implemented")
|
438
|
+
|
439
|
+
def _generate_notation(self):
|
440
|
+
raise RuntimeError("not yet implemented")
|
441
|
+
|
442
|
+
|
443
|
+
def merge_constraints(digit_min=None, digit_max=None, schema_min=None, schema_max=None, config_min=None,
|
444
|
+
config_max=None):
|
445
|
+
logger.debug(
|
446
|
+
"merge numeric constraints: "
|
447
|
+
"digit_min: %s, digit_max: %s, schema_min: %s, schema_max: %s, config_min: %s, config_max: %s",
|
448
|
+
digit_min, digit_max, schema_min, schema_max, config_min, config_max)
|
449
|
+
|
450
|
+
# За основу берем цифровые ограничения (они самые нестрогие)
|
451
|
+
effective_min, effective_max = digit_min, digit_max
|
452
|
+
|
453
|
+
# Применяем схемные ограничения
|
454
|
+
if schema_min is not None:
|
455
|
+
effective_min = max(effective_min, schema_min) if effective_min is not None else schema_min
|
456
|
+
if schema_max is not None:
|
457
|
+
effective_max = min(effective_max, schema_max) if effective_max is not None else schema_max
|
458
|
+
|
459
|
+
# Применяем конфигурационные ограничения с проверкой на конфликт
|
460
|
+
if config_min is not None:
|
461
|
+
if effective_max is not None and config_min > effective_max:
|
462
|
+
logger.warning("can't apply bound from configuration: config_min (%s) > effective_max (%s)",
|
463
|
+
config_min, effective_max)
|
464
|
+
else:
|
465
|
+
effective_min = max(effective_min, config_min) if effective_min is not None else config_min
|
466
|
+
|
467
|
+
if config_max is not None:
|
468
|
+
if effective_min is not None and config_max < effective_min:
|
469
|
+
logger.warning("can't apply bound from configuration: config_max (%s) < effective_min (%s)",
|
470
|
+
config_max, effective_min)
|
471
|
+
else:
|
472
|
+
effective_max = min(effective_max, config_max) if effective_max is not None else config_max
|
473
|
+
|
474
|
+
# Проверяем на конфликт
|
475
|
+
if effective_min is not None and effective_max is not None and effective_min > effective_max:
|
476
|
+
logger.warning("constrains conflict: effective_min (%s) > effective_max (%s). Swap values.",
|
477
|
+
effective_min, effective_max)
|
478
|
+
effective_min, effective_max = effective_max, effective_min
|
479
|
+
|
480
|
+
return effective_min, effective_max
|