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 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
- logger.debug('found %s schemas', len(xsd_files))
51
- for xsd_file in xsd_files:
52
- logger.info('processing schema: %s', xsd_file.name)
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='{{ source_filename }}_{{ uuid }}')
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 re
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: xmlschema.XMLSchema, local_config: GeneratorConfig) -> etree.Element:
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
- self._add_elements(xml_root_element, xsd_root_element, local_config)
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: etree.Element, xsd_element, local_config: GeneratorConfig) -> None:
29
- rnd = self.randomizer.rnd
30
-
31
- xsd_element_type = getattr(xsd_element, 'type', None)
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
- if isinstance(xsd_element_type, XsdAtomicRestriction):
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
- return
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
- elif isinstance(xsd_element_type, XsdAtomicBuiltin):
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
- group_min_occurs = getattr(xsd_element, 'min_occurs', None)
77
- group_max_occurs = getattr(xsd_element, 'max_occurs', None)
78
- group_min_occurs = group_min_occurs if group_min_occurs is not None else 0
79
- group_max_occurs = group_max_occurs if group_max_occurs is not None else 10 # TODO externalize
80
- group_occurs = rnd.randint(group_min_occurs, group_max_occurs)
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
- element_min_occurs = getattr(xsd_child_element_type, 'min_occurs', None)
88
- element_max_occurs = getattr(xsd_child_element_type, 'max_occurs', None)
89
- element_min_occurs = element_min_occurs if element_min_occurs is not None else 0
90
- element_max_occurs = element_max_occurs if element_max_occurs is not None else 10 # TODO externalize
91
- element_occurs = rnd.randint(element_min_occurs, element_max_occurs)
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
- element_min_occurs = getattr(xsd_child_element_type, 'min_occurs', None)
104
- element_max_occurs = getattr(xsd_child_element_type, 'max_occurs', None)
105
- element_min_occurs = element_min_occurs if element_min_occurs is not None else 0
106
- element_max_occurs = element_max_occurs if element_max_occurs is not None else 10 # TODO externalize
107
- element_occurs = rnd.randint(element_min_occurs, element_max_occurs)
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 = rnd.choice(xsd_element)
129
-
130
- element_min_occurs = getattr(xsd_child_element_type, 'min_occurs', None)
131
- element_max_occurs = getattr(xsd_child_element_type, 'max_occurs', None)
132
- element_min_occurs = element_min_occurs if element_min_occurs is not None else 0
133
- element_max_occurs = element_max_occurs if element_max_occurs is not None else 10 # TODO externalize
134
- element_occurs = rnd.randint(element_min_occurs, element_max_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)
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
- return rnd.choice(enumeration)
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
- return self._generate_value_by_type(
225
- xsd_type, target_name,
226
- patterns,
227
- min_length, max_length,
228
- min_value, max_value,
229
- total_digits, fraction_digits
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 _generate_value_by_type(self, xsd_type, target_name, patterns, min_length, max_length, min_value, max_value,
243
- total_digits, fraction_digits) -> str | None:
244
-
245
- type_id = xsd_type.id
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
- random_pattern = rnd.choice(patterns)
302
- xeger = re_gen.xeger(random_pattern.attrib['value'])
303
- xeger = re.sub(r'\s', ' ', xeger)
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 _generate_boolean(self):
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 = rnd.randint(min_value, max_value)
346
+ rnd_int = self.randomizer.integer(min_value, max_value)
329
347
  return str(rnd_int)
330
348
 
331
- def _generate_decimal(self, total_digits, fraction_digits, min_value, max_value):
332
- rnd = self.randomizer.rnd
333
- if total_digits:
334
- if fraction_digits and fraction_digits > 0:
335
- integer_digits = total_digits - fraction_digits
336
- integer_part = rnd.randint(10 ** (integer_digits - 1), (10 ** integer_digits) - 1)
337
- fractional_part = rnd.randint(0, (10 ** fraction_digits) - 1)
338
- return f"{integer_part}.{fractional_part:0{fraction_digits}}"
339
- else:
340
- min_value = 10 ** (total_digits - 1)
341
- max_value = (10 ** total_digits) - 1
342
- rnd_int = rnd.randint(min_value, max_value)
343
- return str(rnd_int)
344
-
345
- rnd_int = rnd.randint(min_value, max_value)
346
- return f"{int(rnd_int / 100)}.{rnd_int % 100:02}"
347
-
348
- def _generate_float(self, min_value, max_value):
349
- rnd = self.randomizer.rnd
350
- rnd_int = rnd.uniform(min_value, max_value)
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
- def _generate_double(self, min_value, max_value):
355
- return self._generate_float(min_value, max_value)
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
- rnd = self.randomizer.rnd
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
@@ -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.rnd = random.Random(seed)
22
- self.fake = Faker(locale='ru_RU')
23
- self.fake.seed_instance(seed)
24
- self.re_gen = rstr.Rstr(self.rnd)
25
-
26
- def ascii_string(self, min_length=-1, max_length=-1):
27
- min_length = min_length if min_length and min_length > -1 else 1
28
- max_length = max_length if max_length and max_length >= min_length else 20
29
- if max_length > 50:
30
- max_length = 50
31
- length = self.rnd.randint(min_length, max_length)
32
- # Генерация случайной строки из букв латиницы
33
- letters = string.ascii_letters # Все буквы латиницы (a-z, A-Z)
34
- return ''.join(self.rnd.choice(letters) for _ in range(length))
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.rnd.randint(start.hour, end.hour)
55
- random_m = self.rnd.randint(start.minute, end.minute)
56
- random_s = self.rnd.randint(start.second, end.second)
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.fake.snils()
134
+ snils = self._fake.snils()
76
135
  return f"{snils[:3]}-{snils[3:6]}-{snils[6:9]} {snils[9:]}"
@@ -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
- 'uuid': lambda: fake.uuid4(),
27
- 'regex': self._rand_regex,
28
- 'any': self._rand_any,
29
- 'number': self._rand_int,
30
- 'date': self._rand_date,
31
-
32
- 'last_name': fake.last_name_male,
33
- 'first_name': fake.first_name_male,
34
- 'middle_name': fake.middle_name_male,
35
- 'address_text': fake.address,
36
- 'administrative_unit': fake.administrative_unit,
37
- 'house_number': fake.building_number,
38
- 'city_name': fake.city_name,
39
- 'postcode': fake.postcode,
40
- 'company_name': fake.company,
41
- 'bank_name': fake.bank,
42
- 'phone_number': fake.phone_number,
43
- 'inn_fl': fake.individuals_inn,
44
- 'inn_ul': fake.businesses_inn,
45
- 'ogrn_ip': fake.individuals_ogrn,
46
- 'ogrn_fl': fake.businesses_ogrn,
47
- 'kpp': fake.kpp,
48
- 'snils_formatted': randomizer.snils_formatted,
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('local_context reset')
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() if not func_args else func_lambda(func_args)
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.2.1
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
- --seed <seed> set randomization seed
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: `{{ source_filename }}_{{ uuid }}` (xsd schema filename + random UUID)
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,,