xmlgenerator 0.2.1__py3-none-any.whl → 0.4.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.
- xmlgenerator/arguments.py +7 -1
- xmlgenerator/bootstrap.py +6 -4
- xmlgenerator/configuration.py +2 -1
- xmlgenerator/generator.py +299 -231
- xmlgenerator/randomization.py +92 -33
- xmlgenerator/substitution.py +49 -52
- {xmlgenerator-0.2.1.dist-info → xmlgenerator-0.4.0.dist-info}/METADATA +6 -3
- xmlgenerator-0.4.0.dist-info/RECORD +14 -0
- xmlgenerator-0.2.1.dist-info/RECORD +0 -14
- {xmlgenerator-0.2.1.dist-info → xmlgenerator-0.4.0.dist-info}/WHEEL +0 -0
- {xmlgenerator-0.2.1.dist-info → xmlgenerator-0.4.0.dist-info}/entry_points.txt +0 -0
- {xmlgenerator-0.2.1.dist-info → xmlgenerator-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {xmlgenerator-0.2.1.dist-info → xmlgenerator-0.4.0.dist-info}/top_level.txt +0 -0
xmlgenerator/arguments.py
CHANGED
@@ -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
|
)
|
xmlgenerator/bootstrap.py
CHANGED
@@ -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)
|
xmlgenerator/configuration.py
CHANGED
@@ -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
|
|
xmlgenerator/generator.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
import logging
|
2
|
-
import
|
2
|
+
from decimal import Decimal
|
3
3
|
|
4
|
-
import xmlschema
|
5
4
|
from lxml import etree
|
6
5
|
from xmlschema.validators import XsdComplexType, XsdAtomicRestriction, XsdTotalDigitsFacet, XsdElement, \
|
7
6
|
XsdGroup, XsdFractionDigitsFacet, XsdLengthFacet, XsdMaxLengthFacet, XsdMinExclusiveFacet, XsdMinInclusiveFacet, \
|
@@ -19,124 +18,162 @@ class XmlGenerator:
|
|
19
18
|
self.randomizer = randomizer
|
20
19
|
self.substitutor = substitutor
|
21
20
|
|
22
|
-
def generate_xml(self, xsd_schema
|
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 != ''}
|
23
24
|
xsd_root_element = xsd_schema.root_elements[0]
|
24
|
-
xml_root_element = etree.Element(xsd_root_element.name)
|
25
|
-
|
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)
|
26
28
|
return xml_root_element
|
27
29
|
|
28
|
-
def _add_elements(self, xml_element
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
logger.debug('fill down element "%s" with type %s', xsd_element.name, type(xsd_element_type).__name__)
|
33
|
-
|
34
|
-
# Add attributes if they are
|
35
|
-
attributes = getattr(xsd_element, 'attributes', dict())
|
36
|
-
if len(attributes) > 0 and xsd_element_type.local_name != 'anyType':
|
37
|
-
logger.debug('add attributes to element %s', xsd_element.name)
|
38
|
-
for attr_name, attr in attributes.items():
|
39
|
-
logger.debug('attribute: %s', attr_name)
|
40
|
-
use = attr.use # optional | required | prohibited
|
41
|
-
if use == 'prohibited':
|
42
|
-
logger.debug('skipped')
|
43
|
-
continue
|
44
|
-
elif use == 'optional':
|
45
|
-
if rnd.random() > local_config.randomization.probability:
|
46
|
-
logger.debug('skipped')
|
47
|
-
continue # skip optional attribute
|
48
|
-
|
49
|
-
attr_value = self._generate_value(attr.type, attr_name, local_config)
|
50
|
-
if attr_value is not None:
|
51
|
-
xml_element.set(attr_name, str(attr_value))
|
52
|
-
logger.debug(f'attribute %s set with value %s', attr_name, attr_value)
|
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
|
53
34
|
|
54
35
|
# Process child elements --------------------------------------------------------------------------------------
|
55
36
|
if isinstance(xsd_element, XsdElement):
|
56
|
-
|
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):
|
57
62
|
text = self._generate_value(xsd_element_type, xsd_element.name, local_config)
|
58
63
|
xml_element.text = text
|
59
|
-
|
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
|
+
|
60
71
|
elif isinstance(xsd_element_type, XsdComplexType):
|
61
72
|
xsd_element_type_content = xsd_element_type.content
|
62
73
|
if isinstance(xsd_element_type_content, XsdGroup):
|
63
|
-
self._add_elements(xml_element, xsd_element_type_content, local_config)
|
74
|
+
self._add_elements(xml_tree, xml_element, xsd_element_type_content, local_config)
|
64
75
|
else:
|
65
76
|
raise RuntimeError()
|
66
|
-
|
67
|
-
text = self._generate_value(xsd_element_type, xsd_element.name, local_config)
|
68
|
-
xml_element.text = text
|
69
|
-
return
|
77
|
+
|
70
78
|
else:
|
71
79
|
raise RuntimeError()
|
72
80
|
|
73
81
|
elif isinstance(xsd_element, XsdGroup):
|
74
82
|
model = xsd_element.model
|
75
83
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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)
|
81
97
|
|
82
98
|
if model == 'all':
|
83
99
|
for _ in range(group_occurs):
|
84
100
|
xsd_group_content = xsd_element.content
|
85
101
|
for xsd_child_element_type in xsd_group_content:
|
86
102
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
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)
|
92
116
|
|
93
117
|
for _ in range(element_occurs):
|
94
118
|
xml_child_element = etree.SubElement(xml_element, xsd_child_element_type.name)
|
95
|
-
self._add_elements(xml_child_element, xsd_child_element_type, local_config)
|
96
|
-
return
|
119
|
+
self._add_elements(xml_tree, xml_child_element, xsd_child_element_type, local_config)
|
97
120
|
|
98
121
|
elif model == 'sequence':
|
99
122
|
for _ in range(group_occurs):
|
100
123
|
xsd_group_content = xsd_element.content
|
101
124
|
for xsd_child_element_type in xsd_group_content:
|
125
|
+
if isinstance(xsd_child_element_type, XsdElement):
|
102
126
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
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)
|
108
140
|
|
109
|
-
if isinstance(xsd_child_element_type, XsdElement):
|
110
141
|
for _ in range(element_occurs):
|
111
142
|
xml_child_element = etree.SubElement(xml_element, xsd_child_element_type.name)
|
112
|
-
self._add_elements(xml_child_element, xsd_child_element_type, local_config)
|
143
|
+
self._add_elements(xml_tree, xml_child_element, xsd_child_element_type, local_config)
|
113
144
|
|
114
145
|
elif isinstance(xsd_child_element_type, XsdGroup):
|
115
146
|
xml_child_element = xml_element
|
116
|
-
self._add_elements(xml_child_element, xsd_child_element_type, local_config)
|
147
|
+
self._add_elements(xml_tree, xml_child_element, xsd_child_element_type, local_config)
|
117
148
|
|
118
149
|
elif isinstance(xsd_child_element_type, XsdAnyElement):
|
119
150
|
xml_child_element = etree.SubElement(xml_element, "Any")
|
120
|
-
self._add_elements(xml_child_element, xsd_child_element_type, local_config)
|
151
|
+
self._add_elements(xml_tree, xml_child_element, xsd_child_element_type, local_config)
|
121
152
|
|
122
153
|
else:
|
123
154
|
raise RuntimeError(xsd_child_element_type)
|
124
|
-
return
|
125
155
|
|
126
156
|
elif model == 'choice':
|
127
157
|
for _ in range(group_occurs):
|
128
|
-
xsd_child_element_type =
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
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)
|
135
173
|
|
136
174
|
for _ in range(element_occurs):
|
137
175
|
xml_child_element = etree.SubElement(xml_element, xsd_child_element_type.name)
|
138
|
-
self._add_elements(xml_child_element, xsd_child_element_type, local_config)
|
139
|
-
return
|
176
|
+
self._add_elements(xml_tree, xml_child_element, xsd_child_element_type, local_config)
|
140
177
|
|
141
178
|
else:
|
142
179
|
raise RuntimeError()
|
@@ -155,79 +192,129 @@ class XmlGenerator:
|
|
155
192
|
if isinstance(xsd_type, XsdComplexType):
|
156
193
|
return None
|
157
194
|
|
158
|
-
rnd = self.randomizer.rnd
|
159
|
-
|
160
|
-
# -------------------------------------------------------------------------------------------------------------
|
161
|
-
# Выясняем ограничения
|
162
|
-
min_length = getattr(xsd_type, 'min_length', None) # None | int
|
163
|
-
max_length = getattr(xsd_type, 'max_length', None) # None | int
|
164
|
-
|
165
|
-
min_value = getattr(xsd_type, 'min_value', None) # None | int
|
166
|
-
max_value = getattr(xsd_type, 'max_value', None) # None
|
167
|
-
|
168
|
-
total_digits = None
|
169
|
-
fraction_digits = None
|
170
|
-
enumeration = getattr(xsd_type, 'enumeration', None)
|
171
|
-
patterns = getattr(xsd_type, 'patterns', None)
|
172
|
-
|
173
|
-
validators = getattr(xsd_type, 'validators', None)
|
174
|
-
for validator in validators:
|
175
|
-
if isinstance(validator, XsdMinExclusiveFacet):
|
176
|
-
min_value = validator.value
|
177
|
-
elif isinstance(validator, XsdMinInclusiveFacet):
|
178
|
-
min_value = validator.value
|
179
|
-
elif isinstance(validator, XsdMaxExclusiveFacet):
|
180
|
-
max_value = validator.value
|
181
|
-
elif isinstance(validator, XsdMaxInclusiveFacet):
|
182
|
-
max_value = validator.value
|
183
|
-
elif isinstance(validator, XsdLengthFacet):
|
184
|
-
min_length = validator.value
|
185
|
-
max_length = validator.value
|
186
|
-
elif isinstance(validator, XsdMinLengthFacet):
|
187
|
-
min_length = validator.value
|
188
|
-
elif isinstance(validator, XsdMaxLengthFacet):
|
189
|
-
max_length = validator.value
|
190
|
-
elif isinstance(validator, XsdTotalDigitsFacet):
|
191
|
-
total_digits = validator.value
|
192
|
-
elif isinstance(validator, XsdFractionDigitsFacet):
|
193
|
-
fraction_digits = validator.value
|
194
|
-
elif isinstance(validator, XsdEnumerationFacets):
|
195
|
-
enumeration = validator.enumeration
|
196
|
-
elif callable(validator):
|
197
|
-
pass
|
198
|
-
else:
|
199
|
-
raise RuntimeError(f"Unhandled validator: {validator}")
|
200
|
-
|
201
|
-
min_length = min_length or -1
|
202
|
-
max_length = max_length or -1
|
203
|
-
|
204
|
-
min_value = min_value or 0
|
205
|
-
max_value = max_value or 100000
|
206
|
-
|
207
195
|
# -------------------------------------------------------------------------------------------------------------
|
208
196
|
# Ищем переопределение значения в конфигурации
|
209
|
-
|
210
197
|
value_override = local_config.value_override
|
211
198
|
is_found, overridden_value = self.substitutor.substitute_value(target_name, value_override.items())
|
212
199
|
if is_found:
|
200
|
+
logger.debug('value resolved: "%s"', overridden_value)
|
213
201
|
return overridden_value
|
214
202
|
|
215
203
|
# -------------------------------------------------------------------------------------------------------------
|
216
204
|
# If there is an enumeration, select a random value from it
|
217
|
-
|
205
|
+
enumeration = getattr(xsd_type, 'enumeration', None)
|
218
206
|
if enumeration is not None:
|
219
|
-
|
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)
|
220
210
|
|
221
|
-
#
|
211
|
+
# -------------------------------------------------------------------------------------------------------------
|
222
212
|
# Генерируем значения для стандартных типов и типов с ограничениями
|
223
213
|
if isinstance(xsd_type, XsdAtomicBuiltin) or isinstance(xsd_type, XsdAtomicRestriction):
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
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
|
230
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
|
231
318
|
|
232
319
|
# -------------------------------------------------------------------------------------------------------------
|
233
320
|
# Проверяем базовый тип
|
@@ -239,120 +326,62 @@ class XmlGenerator:
|
|
239
326
|
|
240
327
|
raise RuntimeError(f"Can't generate value - unhandled type. Target name: {target_name}")
|
241
328
|
|
242
|
-
def
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
base_type = xsd_type.base_type
|
247
|
-
if not type_id:
|
248
|
-
type_id = base_type.id
|
249
|
-
if not type_id:
|
250
|
-
type_id = xsd_type.root_type.id
|
251
|
-
|
252
|
-
match type_id:
|
253
|
-
case 'string':
|
254
|
-
return self._generate_string(target_name, patterns, min_length, max_length)
|
255
|
-
case 'boolean':
|
256
|
-
return self._generate_boolean()
|
257
|
-
case 'integer':
|
258
|
-
return self._generate_integer(total_digits, min_value, max_value)
|
259
|
-
case 'decimal':
|
260
|
-
return self._generate_decimal(total_digits, fraction_digits, min_value, max_value)
|
261
|
-
case 'float':
|
262
|
-
return self._generate_float(min_value, max_value)
|
263
|
-
case 'double':
|
264
|
-
return self._generate_double(min_value, max_value)
|
265
|
-
case 'duration':
|
266
|
-
return self._generate_duration()
|
267
|
-
case 'dateTime':
|
268
|
-
return self._generate_datetime()
|
269
|
-
case 'date':
|
270
|
-
return self._generate_date()
|
271
|
-
case 'time':
|
272
|
-
return self._generate_time()
|
273
|
-
case 'gYearMonth':
|
274
|
-
return self._generate_gyearmonth()
|
275
|
-
case 'gYear':
|
276
|
-
return self._generate_gyear()
|
277
|
-
case 'gMonthDay':
|
278
|
-
return self._generate_gmonthday()
|
279
|
-
case 'gDay':
|
280
|
-
return self._generate_gday()
|
281
|
-
case 'gMonth':
|
282
|
-
return self._generate_gmonth()
|
283
|
-
case 'hexBinary':
|
284
|
-
return self._generate_hex_binary()
|
285
|
-
case 'base64Binary':
|
286
|
-
return self._generate_base64_binary()
|
287
|
-
case 'anyURI':
|
288
|
-
return self._generate_any_uri()
|
289
|
-
case 'QName':
|
290
|
-
return self._generate_qname()
|
291
|
-
case 'NOTATION':
|
292
|
-
return self._generate_notation()
|
293
|
-
case _:
|
294
|
-
raise RuntimeError(type_id)
|
295
|
-
|
296
|
-
def _generate_string(self, target_name, patterns, min_length, max_length):
|
297
|
-
rnd = self.randomizer.rnd
|
298
|
-
re_gen = self.randomizer.re_gen
|
329
|
+
def _generate_boolean(self):
|
330
|
+
return self.randomizer.any(['true', 'false'])
|
331
|
+
|
332
|
+
def _generate_string(self, min_length, max_length, patterns):
|
299
333
|
if patterns is not None:
|
300
334
|
# Генерация строки по regex
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
if min_length > -1 and len(xeger) < min_length:
|
305
|
-
logger.warning(
|
306
|
-
"Possible mistake in schema: %s generated value '%s' can't be shorter than %s",
|
307
|
-
target_name, xeger, min_length
|
308
|
-
)
|
309
|
-
if -1 < max_length < len(xeger):
|
310
|
-
logger.warning(
|
311
|
-
"Possible mistake in schema: %s generated value '%s' can't be longer than %s",
|
312
|
-
target_name, xeger, max_length
|
313
|
-
)
|
314
|
-
return xeger
|
335
|
+
random_enum = self.randomizer.any(patterns)
|
336
|
+
random_pattern = random_enum.attrib['value']
|
337
|
+
return self.randomizer.regex(random_pattern)
|
315
338
|
|
316
339
|
# Иначе генерируем случайную строку
|
317
340
|
return self.randomizer.ascii_string(min_length, max_length)
|
318
341
|
|
319
|
-
def
|
320
|
-
rnd = self.randomizer.rnd
|
321
|
-
return rnd.choice(['true', 'false'])
|
322
|
-
|
323
|
-
def _generate_integer(self, total_digits, min_value, max_value):
|
324
|
-
rnd = self.randomizer.rnd
|
342
|
+
def _generate_integer(self, min_value, max_value, total_digits):
|
325
343
|
if total_digits:
|
326
344
|
min_value = 10 ** (total_digits - 1)
|
327
345
|
max_value = (10 ** total_digits) - 1
|
328
|
-
rnd_int =
|
346
|
+
rnd_int = self.randomizer.integer(min_value, max_value)
|
329
347
|
return str(rnd_int)
|
330
348
|
|
331
|
-
def _generate_decimal(self,
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
rnd_int = round(rnd_int, 2)
|
352
|
-
return str(rnd_int)
|
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)
|
353
369
|
|
354
|
-
|
355
|
-
|
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)
|
356
385
|
|
357
386
|
def _generate_duration(self):
|
358
387
|
raise RuntimeError("not yet implemented")
|
@@ -378,8 +407,7 @@ class XmlGenerator:
|
|
378
407
|
return formatted
|
379
408
|
|
380
409
|
def _generate_gyear(self):
|
381
|
-
|
382
|
-
return str(rnd.randint(2000, 2050))
|
410
|
+
return str(self.randomizer.integer(2000, 2050))
|
383
411
|
|
384
412
|
def _generate_gmonthday(self):
|
385
413
|
random_date = self.randomizer.random_date()
|
@@ -410,3 +438,43 @@ class XmlGenerator:
|
|
410
438
|
|
411
439
|
def _generate_notation(self):
|
412
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
|
xmlgenerator/randomization.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import logging
|
2
2
|
import random
|
3
|
+
import re
|
3
4
|
import string
|
4
5
|
import sys
|
5
6
|
from datetime import datetime, date, time, timedelta
|
@@ -11,66 +12,124 @@ logger = logging.getLogger(__name__)
|
|
11
12
|
|
12
13
|
|
13
14
|
class Randomizer:
|
14
|
-
def __init__(self, seed=None):
|
15
|
+
def __init__(self, seed=None, locale='ru_RU'):
|
15
16
|
if not seed:
|
16
17
|
seed = random.randrange(sys.maxsize)
|
17
18
|
logger.debug('initialize with random seed: %s', seed)
|
18
19
|
else:
|
19
20
|
logger.debug('initialize with provided seed: %s', seed)
|
20
21
|
|
21
|
-
self.
|
22
|
-
self.
|
23
|
-
self.
|
24
|
-
self.
|
25
|
-
|
26
|
-
def
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
return ''
|
22
|
+
self._rnd = random.Random(seed)
|
23
|
+
self._fake = Faker(locale=locale)
|
24
|
+
self._fake.seed_instance(seed)
|
25
|
+
self._rstr = rstr.Rstr(self._rnd)
|
26
|
+
|
27
|
+
def random(self):
|
28
|
+
return self._rnd.random()
|
29
|
+
|
30
|
+
def any(self, options):
|
31
|
+
return self._rnd.choice(options)
|
32
|
+
|
33
|
+
def regex(self, pattern):
|
34
|
+
xeger = self._rstr.xeger(pattern)
|
35
|
+
return re.sub(r'\s', ' ', xeger)
|
36
|
+
|
37
|
+
def uuid(self):
|
38
|
+
return self._fake.uuid4()
|
39
|
+
|
40
|
+
def integer(self, min_value, max_value):
|
41
|
+
return self._rnd.randint(min_value, max_value)
|
42
|
+
|
43
|
+
def float(self, min_value, max_value):
|
44
|
+
return self._rnd.uniform(min_value, max_value)
|
45
|
+
|
46
|
+
def ascii_string(self, min_length, max_length):
|
47
|
+
if min_length is None:
|
48
|
+
min_length = 1
|
49
|
+
if max_length is None:
|
50
|
+
max_length = 20
|
51
|
+
|
52
|
+
length = self._rnd.randint(min_length, max_length)
|
53
|
+
letters = string.ascii_lowercase
|
54
|
+
return ''.join(self._rnd.choice(letters) for _ in range(length)).capitalize()
|
35
55
|
|
36
56
|
def random_date(self, start_date: str = '1990-01-01', end_date: str = '2025-12-31') -> date:
|
37
|
-
# Преобразуем строки в объекты datetime
|
38
57
|
start = date.fromisoformat(start_date)
|
39
58
|
end = date.fromisoformat(end_date)
|
40
59
|
|
41
|
-
# Вычисляем разницу в днях между начальной и конечной датой
|
42
60
|
delta = (end - start).days
|
43
|
-
|
44
|
-
# Генерируем случайное количество дней в пределах delta
|
45
|
-
random_days = self.rnd.randint(0, delta)
|
46
|
-
|
47
|
-
# Добавляем случайное количество дней к начальной дате
|
61
|
+
random_days = self._rnd.randint(0, delta)
|
48
62
|
return start + timedelta(days=random_days)
|
49
63
|
|
50
64
|
def random_time(self, start_time: str = '00:00:00', end_time: str = '23:59:59') -> time:
|
51
65
|
start = time.fromisoformat(start_time)
|
52
66
|
end = time.fromisoformat(end_time)
|
53
67
|
|
54
|
-
random_h = self.
|
55
|
-
random_m = self.
|
56
|
-
random_s = self.
|
68
|
+
random_h = self._rnd.randint(start.hour, end.hour)
|
69
|
+
random_m = self._rnd.randint(start.minute, end.minute)
|
70
|
+
random_s = self._rnd.randint(start.second, end.second)
|
57
71
|
|
58
72
|
return time(hour=random_h, minute=random_m, second=random_s)
|
59
73
|
|
60
74
|
def random_datetime(self, start_date: str = '1990-01-01', end_date: str = '2025-12-31') -> datetime:
|
61
|
-
# Преобразуем строки в объекты datetime
|
62
75
|
start = datetime.strptime(start_date, "%Y-%m-%d")
|
63
76
|
end = datetime.strptime(end_date, "%Y-%m-%d")
|
64
77
|
|
65
|
-
# Вычисляем разницу в днях между начальной и конечной датой
|
66
78
|
delta = (end - start).days
|
67
|
-
|
68
|
-
# Генерируем случайное количество дней в пределах delta
|
69
|
-
random_days = self.rnd.randint(0, delta)
|
70
|
-
|
71
|
-
# Добавляем случайное количество дней к начальной дате
|
79
|
+
random_days = self._rnd.randint(0, delta)
|
72
80
|
return start + timedelta(days=random_days)
|
73
81
|
|
82
|
+
def last_name(self):
|
83
|
+
return self._fake.last_name_male()
|
84
|
+
|
85
|
+
def first_name(self):
|
86
|
+
return self._fake.first_name_male()
|
87
|
+
|
88
|
+
def middle_name(self):
|
89
|
+
return self._fake.middle_name_male()
|
90
|
+
|
91
|
+
def address_text(self):
|
92
|
+
return self._fake.address()
|
93
|
+
|
94
|
+
def administrative_unit(self):
|
95
|
+
return self._fake.administrative_unit()
|
96
|
+
|
97
|
+
def house_number(self):
|
98
|
+
return self._fake.building_number()
|
99
|
+
|
100
|
+
def city_name(self):
|
101
|
+
return self._fake.city_name() if hasattr(self._fake, 'city_name') else self._fake.city()
|
102
|
+
|
103
|
+
def country(self):
|
104
|
+
return self._fake.country()
|
105
|
+
|
106
|
+
def postcode(self):
|
107
|
+
return self._fake.postcode()
|
108
|
+
|
109
|
+
def company_name(self):
|
110
|
+
return self._fake.company()
|
111
|
+
|
112
|
+
def bank_name(self):
|
113
|
+
return self._fake.bank()
|
114
|
+
|
115
|
+
def phone_number(self):
|
116
|
+
return self._fake.phone_number()
|
117
|
+
|
118
|
+
def inn_fl(self):
|
119
|
+
return self._fake.individuals_inn()
|
120
|
+
|
121
|
+
def inn_ul(self):
|
122
|
+
return self._fake.businesses_inn()
|
123
|
+
|
124
|
+
def ogrn_ip(self):
|
125
|
+
return self._fake.individuals_ogrn()
|
126
|
+
|
127
|
+
def ogrn_fl(self):
|
128
|
+
return self._fake.businesses_ogrn()
|
129
|
+
|
130
|
+
def kpp(self):
|
131
|
+
return self._fake.kpp()
|
132
|
+
|
74
133
|
def snils_formatted(self):
|
75
|
-
snils = self.
|
134
|
+
snils = self._fake.snils()
|
76
135
|
return f"{snils[:3]}-{snils[3:6]}-{snils[6:9]} {snils[9:]}"
|
xmlgenerator/substitution.py
CHANGED
@@ -13,62 +13,41 @@ logger = logging.getLogger(__name__)
|
|
13
13
|
|
14
14
|
class Substitutor:
|
15
15
|
def __init__(self, randomizer: Randomizer):
|
16
|
-
fake = randomizer.fake
|
17
16
|
self.randomizer = randomizer
|
18
17
|
self._local_context = {}
|
19
18
|
self._global_context = {}
|
20
19
|
self.providers_dict = {
|
21
|
-
#
|
22
|
-
'source_filename': lambda: self._local_context["source_filename"],
|
23
|
-
'source_extracted': lambda: self._local_context["source_extracted"],
|
24
|
-
'output_filename': lambda: self.get_output_filename(),
|
25
|
-
|
26
|
-
'
|
27
|
-
'regex': self.
|
28
|
-
'
|
29
|
-
'number': self.
|
30
|
-
'date': self.
|
31
|
-
|
32
|
-
'last_name':
|
33
|
-
'first_name':
|
34
|
-
'middle_name':
|
35
|
-
'address_text':
|
36
|
-
'administrative_unit':
|
37
|
-
'house_number':
|
38
|
-
'city_name':
|
39
|
-
'
|
40
|
-
'
|
41
|
-
'
|
42
|
-
'
|
43
|
-
'
|
44
|
-
'
|
45
|
-
'
|
46
|
-
'
|
47
|
-
'
|
48
|
-
'
|
20
|
+
# local scope functions
|
21
|
+
'source_filename': lambda args: self._local_context["source_filename"],
|
22
|
+
'source_extracted': lambda args: self._local_context["source_extracted"],
|
23
|
+
'output_filename': lambda args: self.get_output_filename(),
|
24
|
+
|
25
|
+
'any': lambda args: self._any(args),
|
26
|
+
'regex': lambda args: self._regex(args),
|
27
|
+
'uuid': lambda args: self.randomizer.uuid(),
|
28
|
+
'number': lambda args: self._number(args),
|
29
|
+
'date': lambda args: self._date_formatted(args),
|
30
|
+
|
31
|
+
'last_name': lambda args: self.randomizer.last_name(),
|
32
|
+
'first_name': lambda args: self.randomizer.first_name(),
|
33
|
+
'middle_name': lambda args: self.randomizer.middle_name(),
|
34
|
+
'address_text': lambda args: self.randomizer.address_text(),
|
35
|
+
'administrative_unit': lambda args: self.randomizer.administrative_unit(),
|
36
|
+
'house_number': lambda args: self.randomizer.house_number(),
|
37
|
+
'city_name': lambda args: self.randomizer.city_name(),
|
38
|
+
'country': lambda args: self.randomizer.country(),
|
39
|
+
'postcode': lambda args: self.randomizer.postcode(),
|
40
|
+
'company_name': lambda args: self.randomizer.company_name(),
|
41
|
+
'bank_name': lambda args: self.randomizer.bank_name(),
|
42
|
+
'phone_number': lambda args: self.randomizer.phone_number(),
|
43
|
+
'inn_fl': lambda args: self.randomizer.inn_fl(),
|
44
|
+
'inn_ul': lambda args: self.randomizer.inn_ul(),
|
45
|
+
'ogrn_ip': lambda args: self.randomizer.ogrn_ip(),
|
46
|
+
'ogrn_fl': lambda args: self.randomizer.ogrn_fl(),
|
47
|
+
'kpp': lambda args: self.randomizer.kpp(),
|
48
|
+
'snils_formatted': lambda args: self.randomizer.snils_formatted(),
|
49
49
|
}
|
50
50
|
|
51
|
-
def _rand_regex(self, a):
|
52
|
-
pattern = a.strip("'").strip('"')
|
53
|
-
return self.randomizer.re_gen.xeger(pattern)
|
54
|
-
|
55
|
-
def _rand_any(self, a):
|
56
|
-
args = str(a).split(sep=",")
|
57
|
-
value = self.randomizer.rnd.choice(args)
|
58
|
-
value = value.strip(' ').strip("'").strip('"')
|
59
|
-
return value
|
60
|
-
|
61
|
-
def _rand_int(self, a):
|
62
|
-
args = str(a).split(sep=",")
|
63
|
-
return str(self.randomizer.rnd.randint(int(args[0]), int(args[1])))
|
64
|
-
|
65
|
-
def _rand_date(self, a):
|
66
|
-
args = str(a).split(sep=",")
|
67
|
-
date_from = args[0].strip(' ').strip("'").strip('"')
|
68
|
-
date_until = args[1].strip(' ').strip("'").strip('"')
|
69
|
-
random_date = self.randomizer.random_datetime(date_from, date_until)
|
70
|
-
return random_date.strftime('%Y%m%d') # TODO externalize pattern
|
71
|
-
|
72
51
|
def reset_context(self, xsd_filename, config_local):
|
73
52
|
self._local_context.clear()
|
74
53
|
self._local_context["source_filename"] = xsd_filename
|
@@ -82,7 +61,7 @@ class Substitutor:
|
|
82
61
|
resolved_value = self._process_expression(output_filename)
|
83
62
|
self._local_context['output_filename'] = resolved_value
|
84
63
|
|
85
|
-
logger.debug('
|
64
|
+
logger.debug('reset local context...')
|
86
65
|
logger.debug('local_context["source_filename"] = %s', xsd_filename)
|
87
66
|
logger.debug('local_context["source_extracted"] = %s (extracted with regexp %s)', source_extracted, source_filename)
|
88
67
|
logger.debug('local_context["output_filename"] = %s', resolved_value)
|
@@ -115,7 +94,7 @@ class Substitutor:
|
|
115
94
|
if not func_lambda:
|
116
95
|
raise RuntimeError(f"Unknown function {func_name}")
|
117
96
|
|
118
|
-
provider_func = lambda: func_lambda(
|
97
|
+
provider_func = lambda: func_lambda(func_args)
|
119
98
|
|
120
99
|
match func_mod:
|
121
100
|
case None:
|
@@ -136,3 +115,21 @@ class Substitutor:
|
|
136
115
|
|
137
116
|
logger.debug('expression resolved to value: %s', result_value)
|
138
117
|
return result_value
|
118
|
+
|
119
|
+
def _any(self, args):
|
120
|
+
separated_args = str(args).split(sep=",")
|
121
|
+
options = [i.strip(' ').strip("'").strip('"') for i in separated_args]
|
122
|
+
return self.randomizer.any(options)
|
123
|
+
|
124
|
+
def _regex(self, args):
|
125
|
+
pattern = args.strip("'").strip('"')
|
126
|
+
return self.randomizer.regex(pattern)
|
127
|
+
|
128
|
+
def _number(self, args):
|
129
|
+
left_bound, right_bound = (int(i) for i in str(args).split(sep=","))
|
130
|
+
return str(self.randomizer.integer(left_bound, right_bound))
|
131
|
+
|
132
|
+
def _date_formatted(self, args):
|
133
|
+
date_from, date_until = (i.strip(' ').strip("'").strip('"') for i in str(args).split(sep=","))
|
134
|
+
random_date = self.randomizer.random_datetime(date_from, date_until)
|
135
|
+
return random_date.strftime("%Y%m%d")
|
@@ -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
|
@@ -0,0 +1,14 @@
|
|
1
|
+
xmlgenerator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
xmlgenerator/arguments.py,sha256=0WHKt7eOS7M3_R-rYdp_52Q8rgArCF9VlIDkPVP_8dk,4784
|
3
|
+
xmlgenerator/bootstrap.py,sha256=T_Xy5PElb75EuyKIwXUGkQ2mntt3v2RwC1ulFI-CZnM,3654
|
4
|
+
xmlgenerator/configuration.py,sha256=JYhz_lONxd0faUiZHG-TVEs6yocn0s__Ulwtcvq9eDs,5946
|
5
|
+
xmlgenerator/generator.py,sha256=0hVlvod26PhR3-O2zEi-Tzdq4uCU13Xjeco5Srte5tQ,23228
|
6
|
+
xmlgenerator/randomization.py,sha256=OXh8ZmOKEU6AieGDl67d19i9MhobJKShONT19gDOFyo,4009
|
7
|
+
xmlgenerator/substitution.py,sha256=gaNcVqdDt3vbcBR8Pxf1mxo_qp8tTcd09VvVKnwlIA0,6019
|
8
|
+
xmlgenerator/validation.py,sha256=uCJjS5YmRDlAp9C-5Rd4E2Brh6_3WOG2-dSGxDiaH14,2023
|
9
|
+
xmlgenerator-0.4.0.dist-info/licenses/LICENSE,sha256=QlXK8O3UcoAYUYwVJNgB9MSM7O94ogNo_1hd9GzznUQ,1070
|
10
|
+
xmlgenerator-0.4.0.dist-info/METADATA,sha256=0Hi7lAhUvXxo7nXcOcxIFZ1ufmxFli5Hn4ROEt1eZHU,12935
|
11
|
+
xmlgenerator-0.4.0.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
|
12
|
+
xmlgenerator-0.4.0.dist-info/entry_points.txt,sha256=ly9hKr3o4AzFUkelBZNRzyKYf-Ld4kfcffvBu1oHq54,61
|
13
|
+
xmlgenerator-0.4.0.dist-info/top_level.txt,sha256=jr7FbMBm8MQ6j8I_-nWzQQEseXzwSCZNXgrkWuk9P4E,13
|
14
|
+
xmlgenerator-0.4.0.dist-info/RECORD,,
|
@@ -1,14 +0,0 @@
|
|
1
|
-
xmlgenerator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
xmlgenerator/arguments.py,sha256=E0b5ndlGAxu39OgRatqmPdWSkS5EvCTLUEMKFgezi7c,4612
|
3
|
-
xmlgenerator/bootstrap.py,sha256=bPVHugpMSkv54_8Kxe0RPqyvIxC3cci-1yehntZm_YY,3510
|
4
|
-
xmlgenerator/configuration.py,sha256=DiYUpNCjkerjr73yD209q8FJwGWgipbwWXuLkt25rQM,5903
|
5
|
-
xmlgenerator/generator.py,sha256=Ci3xuOoG2jpTcoRa2c1BEDI_4hIfEd3iU05tcgB0paM,18877
|
6
|
-
xmlgenerator/randomization.py,sha256=LK4ZwSZdyks7qV0OOxKsGyKA0q660ID7x2DeU9gvL_I,3261
|
7
|
-
xmlgenerator/substitution.py,sha256=mD827EbgYg2mTp-PNcvsERJtVUKaLvaoOoQmGnq5zBo,5595
|
8
|
-
xmlgenerator/validation.py,sha256=uCJjS5YmRDlAp9C-5Rd4E2Brh6_3WOG2-dSGxDiaH14,2023
|
9
|
-
xmlgenerator-0.2.1.dist-info/licenses/LICENSE,sha256=QlXK8O3UcoAYUYwVJNgB9MSM7O94ogNo_1hd9GzznUQ,1070
|
10
|
-
xmlgenerator-0.2.1.dist-info/METADATA,sha256=zfA1y1vFm58sX2tRolF6G8LngaBGViSPp8C2Xp7_PvY,12801
|
11
|
-
xmlgenerator-0.2.1.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
|
12
|
-
xmlgenerator-0.2.1.dist-info/entry_points.txt,sha256=ly9hKr3o4AzFUkelBZNRzyKYf-Ld4kfcffvBu1oHq54,61
|
13
|
-
xmlgenerator-0.2.1.dist-info/top_level.txt,sha256=jr7FbMBm8MQ6j8I_-nWzQQEseXzwSCZNXgrkWuk9P4E,13
|
14
|
-
xmlgenerator-0.2.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|