Ya construye XSD aunque con errores

This commit is contained in:
perro tuerto 2023-01-27 19:58:35 -08:00
parent adc543e608
commit 5e21d1f3b3
2 changed files with 124 additions and 37 deletions

View File

@ -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
View File

@ -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):