Ya construye XSD aunque con errores
This commit is contained in:
parent
adc543e608
commit
5e21d1f3b3
32
README.md
32
README.md
|
@ -73,10 +73,10 @@ To better understad what YASD does, see the inputs and outputs:
|
||||||
...
|
...
|
||||||
children_order: all|choice|sequence
|
children_order: all|choice|sequence
|
||||||
children:
|
children:
|
||||||
- name: element_name
|
- ref: element_name
|
||||||
maxOccurs: INTEGER
|
maxOccurs: INTEGER|unbounded
|
||||||
minOccurs: INTEGER
|
minOccurs: INTEGER
|
||||||
- group: group_name
|
- group_ref: group_name
|
||||||
...
|
...
|
||||||
|
|
||||||
### Attribute (ATTR) structure
|
### Attribute (ATTR) structure
|
||||||
|
@ -97,7 +97,7 @@ To better understad what YASD does, see the inputs and outputs:
|
||||||
attribute_group: true|false
|
attribute_group: true|false
|
||||||
children_order: all|choice|sequence
|
children_order: all|choice|sequence
|
||||||
children:
|
children:
|
||||||
- name: element_name
|
- ref: element_or_attribute_name
|
||||||
maxOccurs: INTEGER|unbounded
|
maxOccurs: INTEGER|unbounded
|
||||||
minOccurs: INTEGER
|
minOccurs: INTEGER
|
||||||
|
|
||||||
|
@ -137,7 +137,7 @@ Mandatory.
|
||||||
|
|
||||||
### `name`
|
### `name`
|
||||||
|
|
||||||
Indicates element or attribute name.
|
Indicates element, attribute or group name.
|
||||||
|
|
||||||
Mandatory.
|
Mandatory.
|
||||||
|
|
||||||
|
@ -152,6 +152,12 @@ Naming rules:
|
||||||
periods
|
periods
|
||||||
- Element names cannot contain spaces
|
- Element names cannot contain spaces
|
||||||
|
|
||||||
|
### `ref`
|
||||||
|
|
||||||
|
References element or attribute by name.
|
||||||
|
|
||||||
|
Mandatory.
|
||||||
|
|
||||||
### `description`
|
### `description`
|
||||||
|
|
||||||
Indicates element or attribute description in human readable form.
|
Indicates element or attribute description in human readable form.
|
||||||
|
@ -174,13 +180,13 @@ Allowed types:
|
||||||
|
|
||||||
Chart:
|
Chart:
|
||||||
|
|
||||||
| type | elements | text | attributes |
|
| type | elements | text | attributes |
|
||||||
|------------|:--------:|:----:|:----------:|
|
|-------------|:--------:|:----:|:----------:|
|
||||||
|simple | ✗ | ✓ | ✗ |
|
| simple | ✗ | ✓ | ✗ |
|
||||||
|empty | ✗ | ✗ | ✓ |
|
| empty | ✗ | ✗ | ✓ |
|
||||||
|no_text | ✓ | ✗ | ✓ |
|
| no_text | ✓ | ✗ | ✓ |
|
||||||
|no_elements | ✗ | ✓ | ✓ |
|
| no_elements | ✗ | ✓ | ✓ |
|
||||||
|mixed | ✓ | ✓ | ✓ |
|
| mixed | ✓ | ✓ | ✓ |
|
||||||
|
|
||||||
> **Note 1**: read "elements" and "text" with "direct children…" as prefix.
|
> **Note 1**: read "elements" and "text" with "direct children…" as prefix.
|
||||||
|
|
||||||
|
@ -353,7 +359,7 @@ Valid value is non negative integer.
|
||||||
|
|
||||||
### `group`
|
### `group`
|
||||||
|
|
||||||
Indicates group name.
|
References group name.
|
||||||
|
|
||||||
Optional.
|
Optional.
|
||||||
|
|
||||||
|
|
129
yasd.py
129
yasd.py
|
@ -2,11 +2,12 @@
|
||||||
# (c) 2023 Perro Tuerto <hi@perrotuerto.blog>.
|
# (c) 2023 Perro Tuerto <hi@perrotuerto.blog>.
|
||||||
# Founded by Mexican Academy of Language <https://academia.org.mx>.
|
# Founded by Mexican Academy of Language <https://academia.org.mx>.
|
||||||
# Licensed under GPLv3 <https://www.gnu.org/licenses/gpl-3.0.en.html>.
|
# Licensed under GPLv3 <https://www.gnu.org/licenses/gpl-3.0.en.html>.
|
||||||
# Requirements: python > 3.10, pyyaml, lxml, bs4, rich
|
# Requirements: python > 3.10, pyyaml, lxml, bs4, xmlschema, rich
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import yaml
|
import yaml
|
||||||
import argparse
|
import argparse
|
||||||
|
import xmlschema
|
||||||
import urllib.request
|
import urllib.request
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
@ -27,6 +28,7 @@ class YASD:
|
||||||
quiet=False,
|
quiet=False,
|
||||||
log=False,
|
log=False,
|
||||||
stdout=False,
|
stdout=False,
|
||||||
|
validate=False,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Performs YASD actions directly.
|
Performs YASD actions directly.
|
||||||
|
@ -44,11 +46,13 @@ class YASD:
|
||||||
:type log: True or False
|
:type log: True or False
|
||||||
:param stdout: if conversion goes to stdout or not; 'False' by default
|
:param stdout: if conversion goes to stdout or not; 'False' by default
|
||||||
:type stdout: True or False
|
:type stdout: True or False
|
||||||
|
:param validate: if XSD is validated; 'False' by default
|
||||||
|
:type validate: True or False
|
||||||
:return: Output data; str on 'document'; bs4.element.Tag on 'convert'
|
:return: Output data; str on 'document'; bs4.element.Tag on 'convert'
|
||||||
or 'sample'; YAML dict on 'check'
|
or 'sample'; YAML dict on 'check'
|
||||||
:rtype: str or bs4.element.Tag or dict
|
:rtype: str or bs4.element.Tag or dict
|
||||||
"""
|
"""
|
||||||
yasd = YASD(indata, outfile, quiet, log, stdout)
|
yasd = YASD(indata, outfile, quiet, log, stdout, validate)
|
||||||
yasd.msgr.run(f"action_{action}")
|
yasd.msgr.run(f"action_{action}")
|
||||||
match action:
|
match action:
|
||||||
case "document":
|
case "document":
|
||||||
|
@ -61,7 +65,13 @@ class YASD:
|
||||||
return yasd.yaml
|
return yasd.yaml
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, indata=None, outfile=None, quiet=False, log=False, stdout=False
|
self,
|
||||||
|
indata=None,
|
||||||
|
outfile=None,
|
||||||
|
quiet=False,
|
||||||
|
log=False,
|
||||||
|
stdout=False,
|
||||||
|
validate=False,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Inits YASD object.
|
Inits YASD object.
|
||||||
|
@ -74,11 +84,16 @@ class YASD:
|
||||||
:type quiet: True or False
|
:type quiet: True or False
|
||||||
:param log: If messages are write in a file or not; 'False' by default
|
:param log: If messages are write in a file or not; 'False' by default
|
||||||
:type log: True or False
|
:type log: True or False
|
||||||
|
:param stdout: if conversion goes to stdout or not; 'False' by default
|
||||||
|
:type stdout: True or False
|
||||||
|
:param validate: if XSD is validated; 'False' by default
|
||||||
|
:type validate: True or False
|
||||||
"""
|
"""
|
||||||
self.msgr = YASDMessenger(quiet=quiet, log=log)
|
self.msgr = YASDMessenger(quiet=quiet, log=log)
|
||||||
self.yaml = YASDCheck(indata, self.msgr).yaml
|
self.yaml = YASDCheck(indata, self.msgr).yaml
|
||||||
self.formatter = XMLFormatter(indent=2)
|
self.formatter = XMLFormatter(indent=2)
|
||||||
self.stdout = stdout
|
self.stdout = stdout
|
||||||
|
self.validate = validate
|
||||||
if outfile is None:
|
if outfile is None:
|
||||||
self.outfile = None
|
self.outfile = None
|
||||||
else:
|
else:
|
||||||
|
@ -92,7 +107,11 @@ class YASD:
|
||||||
:rtype: bs4.element.Tag
|
:rtype: bs4.element.Tag
|
||||||
"""
|
"""
|
||||||
self.xsd = YASDXSD(self.yaml, self.msgr).xsd
|
self.xsd = YASDXSD(self.yaml, self.msgr).xsd
|
||||||
return self.__output(self.xsd)
|
out = self.__output(self.xsd)
|
||||||
|
if self.validate:
|
||||||
|
return YASDCheck.xsd(out, self.msgr)
|
||||||
|
else:
|
||||||
|
return out
|
||||||
|
|
||||||
def sample(self):
|
def sample(self):
|
||||||
"""
|
"""
|
||||||
|
@ -118,13 +137,12 @@ class YASD:
|
||||||
"""
|
"""
|
||||||
Prints in the terminal or writes into a file.
|
Prints in the terminal or writes into a file.
|
||||||
|
|
||||||
:return: Output data; str if outdata comes from YASDRST; otherwise
|
:return: Output data
|
||||||
bs4.element.Tag
|
:rtype: str
|
||||||
:rtype: str or bs4.element.Tag
|
|
||||||
"""
|
"""
|
||||||
|
if type(outdata) is not str:
|
||||||
|
outdata = outdata.prettify(formatter=self.formatter)
|
||||||
if self.stdout:
|
if self.stdout:
|
||||||
if type(outdata) is not str:
|
|
||||||
outdata = outdata.prettify(formatter=self.formatter)
|
|
||||||
if self.outfile is None:
|
if self.outfile is None:
|
||||||
print(outdata)
|
print(outdata)
|
||||||
else:
|
else:
|
||||||
|
@ -184,7 +202,7 @@ class YASDXSD:
|
||||||
|
|
||||||
def __build_attributes(self):
|
def __build_attributes(self):
|
||||||
"""
|
"""
|
||||||
Builds attributes nodes for XSD.
|
Builds attribute nodes for XSD.
|
||||||
|
|
||||||
Attributes are always simple types.
|
Attributes are always simple types.
|
||||||
"""
|
"""
|
||||||
|
@ -192,8 +210,18 @@ class YASDXSD:
|
||||||
self.__build_simple(self.__sanitize(el), tag="attribute")
|
self.__build_simple(self.__sanitize(el), tag="attribute")
|
||||||
|
|
||||||
def __build_groups(self):
|
def __build_groups(self):
|
||||||
# TODO build groups
|
"""
|
||||||
...
|
Builds group nodes for XSD.
|
||||||
|
"""
|
||||||
|
for el in self.yaml["groups"]:
|
||||||
|
if "attribute_group" in el.keys():
|
||||||
|
# TODO build attributeGroup
|
||||||
|
...
|
||||||
|
else:
|
||||||
|
element = self.xsd.new_tag("group", nsprefix="xs")
|
||||||
|
indicator = self.__build_indicator(el)
|
||||||
|
element.append(indicator)
|
||||||
|
self.xsd.schema.append(element)
|
||||||
|
|
||||||
def __build_simple(self, el, tag="element"):
|
def __build_simple(self, el, tag="element"):
|
||||||
"""
|
"""
|
||||||
|
@ -224,9 +252,7 @@ class YASDXSD:
|
||||||
complex_type = self.__build_complex_type(el)
|
complex_type = self.__build_complex_type(el)
|
||||||
self.__add_references(complex_type, el, is_attr=True)
|
self.__add_references(complex_type, el, is_attr=True)
|
||||||
if "children" in el.keys():
|
if "children" in el.keys():
|
||||||
other_tag = el["children_order"]
|
indicator = self.__build_indicator(el)
|
||||||
indicator = self.xsd.new_tag(other_tag, nsprefix="xs")
|
|
||||||
self.__add_references(indicator, el)
|
|
||||||
complex_type.append(indicator)
|
complex_type.append(indicator)
|
||||||
element.append(complex_type)
|
element.append(complex_type)
|
||||||
self.xsd.schema.append(element)
|
self.xsd.schema.append(element)
|
||||||
|
@ -293,6 +319,17 @@ class YASDXSD:
|
||||||
container.append(restriction)
|
container.append(restriction)
|
||||||
root.append(container)
|
root.append(container)
|
||||||
|
|
||||||
|
def __build_indicator(self, el):
|
||||||
|
"""
|
||||||
|
Builds indicator node for XSD.
|
||||||
|
|
||||||
|
:param dict el: YASD element
|
||||||
|
"""
|
||||||
|
other_tag = el["children_order"]
|
||||||
|
indicator = self.xsd.new_tag(other_tag, nsprefix="xs")
|
||||||
|
self.__add_references(indicator, el)
|
||||||
|
return indicator
|
||||||
|
|
||||||
def __get_base(self, restrictions):
|
def __get_base(self, restrictions):
|
||||||
"""
|
"""
|
||||||
Gets restriction data type.
|
Gets restriction data type.
|
||||||
|
@ -325,7 +362,7 @@ class YASDXSD:
|
||||||
if key in el.keys() and "group" in el[key][0].keys():
|
if key in el.keys() and "group" in el[key][0].keys():
|
||||||
tag, name = "group", "group"
|
tag, name = "group", "group"
|
||||||
else:
|
else:
|
||||||
name = "name"
|
name = "ref"
|
||||||
return key, tag, name
|
return key, tag, name
|
||||||
|
|
||||||
def __add_references(self, root, el, is_attr=False):
|
def __add_references(self, root, el, is_attr=False):
|
||||||
|
@ -342,6 +379,10 @@ class YASDXSD:
|
||||||
for element in el[key]:
|
for element in el[key]:
|
||||||
node = self.xsd.new_tag(tag, nsprefix="xs")
|
node = self.xsd.new_tag(tag, nsprefix="xs")
|
||||||
node["ref"] = element[name]
|
node["ref"] = element[name]
|
||||||
|
if "maxOccurs" in element.keys():
|
||||||
|
node["maxOccurs"] = element["maxOccurs"]
|
||||||
|
if "minOccurs" in element.keys():
|
||||||
|
node["minOccurs"] = element["minOccurs"]
|
||||||
root.append(node)
|
root.append(node)
|
||||||
del el[key]
|
del el[key]
|
||||||
|
|
||||||
|
@ -427,10 +468,12 @@ class YASDCheck:
|
||||||
|
|
||||||
def file(filepath=None, msgr=None):
|
def file(filepath=None, msgr=None):
|
||||||
"""
|
"""
|
||||||
Verififes if file exists.
|
Verifies if file exists.
|
||||||
|
|
||||||
:param filepath: File path
|
:param filepath: File path
|
||||||
:type filepath: None or Path
|
:type filepath: None or Path
|
||||||
|
:param msgr: Messenger object
|
||||||
|
:type msgr: None or YASDMessenger
|
||||||
"""
|
"""
|
||||||
msgr = YASDCheck.messenger(msgr)
|
msgr = YASDCheck.messenger(msgr)
|
||||||
if type(filepath).__module__ != "pathlib":
|
if type(filepath).__module__ != "pathlib":
|
||||||
|
@ -439,6 +482,40 @@ class YASDCheck:
|
||||||
msgr.run("invalid_input", level="error", file=filepath)
|
msgr.run("invalid_input", level="error", file=filepath)
|
||||||
return filepath.resolve()
|
return filepath.resolve()
|
||||||
|
|
||||||
|
def url(key="readme", msgr=None):
|
||||||
|
"""
|
||||||
|
Verifies if remote file exists
|
||||||
|
|
||||||
|
:param str key: YASDMessenger string key
|
||||||
|
:param msgr: Messenger object
|
||||||
|
:type msgr: None or YASDMessenger
|
||||||
|
"""
|
||||||
|
# TODO if YASD becomes pip package, the fetched files should be local
|
||||||
|
# Remove urllib import if that is the case
|
||||||
|
msgr = YASDCheck.messenger(msgr)
|
||||||
|
try:
|
||||||
|
url = YASDMessenger.keys()[key]
|
||||||
|
msgr.run("fetching", url=url)
|
||||||
|
return urllib.request.urlopen(url).read().decode("utf-8")
|
||||||
|
except Exception:
|
||||||
|
msgr.run("no_url", level="error", url=url)
|
||||||
|
|
||||||
|
def xsd(xsd, msgr=None):
|
||||||
|
"""
|
||||||
|
Validates XSD.
|
||||||
|
|
||||||
|
:param str xsd: XSD as XML string
|
||||||
|
:param msgr: Messenger object
|
||||||
|
:type msgr: None or YASDMessenger
|
||||||
|
"""
|
||||||
|
msgr = YASDCheck.messenger(msgr)
|
||||||
|
msgr.run("validating")
|
||||||
|
try:
|
||||||
|
xmlschema.XMLSchema(xsd)
|
||||||
|
except xmlschema.validators.exceptions.XMLSchemaParseError as error:
|
||||||
|
error = str(error).replace("\n", "\n ")
|
||||||
|
msgr.run("no_valid", error=error, level="error")
|
||||||
|
|
||||||
def __init__(self, indata=None, messenger=None):
|
def __init__(self, indata=None, messenger=None):
|
||||||
"""
|
"""
|
||||||
Inits YASD validator.
|
Inits YASD validator.
|
||||||
|
@ -502,20 +579,26 @@ class YASDMessenger:
|
||||||
Academy of Language <https://academia.org.mx>. Licensed under GPLv3
|
Academy of Language <https://academia.org.mx>. Licensed under GPLv3
|
||||||
<https://www.gnu.org/licenses/gpl-3.0.en.html>.
|
<https://www.gnu.org/licenses/gpl-3.0.en.html>.
|
||||||
""",
|
""",
|
||||||
|
"readme": "https://gitlab.com/perrotuerto_personal/codigo/yasd/-/raw/no-masters/README.md",
|
||||||
|
"w3": "https://www.w3.org/2001/XMLSchema.xsd",
|
||||||
"help_action": "action to perform",
|
"help_action": "action to perform",
|
||||||
"help_input": "input file in YAML format",
|
"help_input": "input file in YAML format",
|
||||||
"help_output": "output file",
|
"help_output": "output file",
|
||||||
"help_quiet": "enable quiet mode",
|
"help_quiet": "enable quiet mode",
|
||||||
"help_log": "write log",
|
"help_log": "write log",
|
||||||
"action_convert": "creating XSD schema",
|
"action_convert": "creating XSD schema",
|
||||||
"action_check": "checking YASD",
|
"action_check": "checking YASD structure",
|
||||||
"action_sample": "creating XML sample",
|
"action_sample": "creating XML sample",
|
||||||
"action_document": "creating RST documentation",
|
"action_document": "creating RST documentation",
|
||||||
"invalid_level": "invalid log level '@lvl'",
|
"invalid_level": "invalid log level '@lvl'",
|
||||||
"invalid_input": "invalid file '@file'",
|
"invalid_input": "invalid file '@file'",
|
||||||
"invalid_yaml": "invalid YAML structure",
|
"invalid_yaml": "invalid YAML structure",
|
||||||
"no_yaml": "YAML dict needed",
|
"no_url": "failed to fetch '@url'",
|
||||||
"no_input": "input file needed",
|
"no_input": "input file needed",
|
||||||
|
"no_yaml": "YAML dict needed",
|
||||||
|
"no_valid": "XSD schema has the following error:\n @error",
|
||||||
|
"fetching": "fetching '@url'",
|
||||||
|
"validating": "validating XSD schema",
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, quiet=False, log=False):
|
def __init__(self, quiet=False, log=False):
|
||||||
|
@ -582,10 +665,7 @@ class YASDCLI:
|
||||||
"""
|
"""
|
||||||
Prints README as manual.
|
Prints README as manual.
|
||||||
"""
|
"""
|
||||||
# TODO if YASD becomes pip package, it should load local README
|
raw = YASDCheck.url()
|
||||||
# Remove urllib import if that is the case
|
|
||||||
url = "https://gitlab.com/perrotuerto_personal/codigo/yasd/-/raw/no-masters/README.md"
|
|
||||||
raw = urllib.request.urlopen(url).read().decode("utf-8")
|
|
||||||
raw = raw.replace("## Table of Contents\n\n[TOC]\n\n", "")
|
raw = raw.replace("## Table of Contents\n\n[TOC]\n\n", "")
|
||||||
md = Markdown(raw)
|
md = Markdown(raw)
|
||||||
console = Console()
|
console = Console()
|
||||||
|
@ -607,7 +687,8 @@ class YASDCLI:
|
||||||
args.output,
|
args.output,
|
||||||
args.quiet,
|
args.quiet,
|
||||||
args.log,
|
args.log,
|
||||||
True,
|
stdout=True,
|
||||||
|
validate=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init_parser(self):
|
def __init_parser(self):
|
||||||
|
|
Loading…
Reference in New Issue