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