xsd_process.py 5.24 KB
Newer Older
import logging
from pathlib import Path

from xmlschema.etree import etree_tostring
from xmlschema import XMLSchema, XMLSchemaParseError


def BuildSchemaDictonary (fileList):
    if len(fileList) == 0:
        logging.info("No schema files provided")
        return []

    logging.info("Schema locations:")
    schemaLocations = []
    for schemaFile in fileList:
        try:
            xs = XMLSchema(schemaFile, validation='skip')
            schemaLocations.append((xs.default_namespace, str(Path(schemaFile).resolve())))
            logging.info(" [ {0}  ->  {1} ]".format(xs.default_namespace, schemaFile))
        except XMLSchemaParseError as ex:
            logging.warning (" [ {0} failed to parse:  {1} ]".format(schemaFile, ex))
    return schemaLocations


def BuildSchema (coreFile, fileList = None):
    schemaLocations = []
    if fileList and len(fileList) > 0:
        schemaLocations = BuildSchemaDictonary(fileList)

    coreSchema = XMLSchema(str(Path(coreFile)), locations=schemaLocations)
    return coreSchema


def ValidateXSDFiles (fileList):
    if len(fileList) == 0:
        logging.info("No schema files provided")
        return {}
    
    schemaLocations = BuildSchemaDictonary(fileList)
    errors = {}

    logging.info("Schema validation:")
    for schemaFile in fileList:
        try:
            schema = XMLSchema(schemaFile, locations = schemaLocations, validation="lax")
            logging.info(schemaFile + ": OK")
            errors[schemaFile] = [f"{etree_tostring(e.elem, e.namespaces, '  ', 20)} - {e.message}" for e in schema.all_errors]
        except XMLSchemaParseError as ex:
            logging.warning(schemaFile + ": Failed validation ({0})".format(ex.message))
            if (ex.schema_url) and (ex.schema_url != ex.origin_url):
                logging.warning("  Error comes from {0}, suppressing".format(ex.schema_url))
                errors[schemaFile] = []                
            else:
                errors[schemaFile] = [ex]
    return errors


def ValidateAllXSDFilesInPath (path):
    schemaGlob = [str(f) for f in Path(path).rglob("*.xsd")]
    return ValidateXSDFiles(schemaGlob)


def ValidateInstanceDocuments (coreFile, supportingSchemas, instanceDocs):
    if (instanceDocs is None) or len(instanceDocs) == 0:
        logging.warning ("No instance documents provided")
        return []

    schema = BuildSchema(coreFile, supportingSchemas)
    errors = []
    for instanceDoc in instanceDocs:
        try:
            schema.validate(instanceDoc)
            logging.info ("{0} passed validation".format(instanceDoc))
        except Exception as ex:
            logging.error ("{0} failed validation: {1}".format(instanceDoc, ex))
    return errors


def processResults (results, stageName):
    """
    Counts the number of errors and writes out the output per filename

    :param results: List of filenames (str or Pathlib Path)
    :param stageName: Name to decorate the output with
    :returns: The number of files which had errors
    """    
    print("")
    errorCount = sum([1 for r in results.values() if not r['ok']])
    logging.info(f"{errorCount} {stageName} errors encountered")
    
    print(f"{'-':-<60}")
    print(f"{stageName} results:")
    print(f"{'-':-<60}")
    for filename, result in results.items():
        print(f" {filename:.<55}{'..OK' if result['ok'] else 'FAIL'}")
        if not result['ok']:
            if isinstance(result['message'], list):
                for thing in result['message']:
                    print(f"    {thing['message']}")
            else:
                print(f"    {result['message']}")
    
    print(f"{'-':-<60}")
    print(f"{stageName} errors: {errorCount}")
    print(f"{'-':-<60}")
 
    return errorCount


if __name__ == '__main__':
    logging.basicConfig(level=logging.DEBUG)

    logging.info('Searching for XSD files')
    fileList = list(Path(".").rglob("*.xsd")) + list(Path(".").rglob("*.xsd"))
    logging.info(f'{len(fileList)} XSD files found')
    for file in fileList:
        logging.debug(f'  {file}')
    
    ignoreList = Path('testing/xsd_ignore.txt').read_text().splitlines()
    ignoredFiles = []
    for ignore in ignoreList:
        logging.debug(f'Ignoring pattern {ignore}')
        for file in fileList:
            if ignore in str(file):
                ignoredFiles.append(file)
                logging.debug(f" Ignoring {str(file)} as contains {ignore}")
    ignoredFiles = list(set(ignoredFiles))
    logging.info(f'{len(ignoredFiles)} files ignored')
    for file in ignoredFiles:
        logging.debug(f'  {file}')

    fileList = [file for file in fileList if file not in ignoredFiles]
    logging.info(f'{len(fileList)} files to process')
    for file in fileList:
        logging.debug(f'  {file}')

    if len(fileList) == 0:
        logging.warning ("No files specified")
        exit(0)
    
    logging.info("Parsing ASN1 files")
    parseResults = syntaxCheckXSD(fileList)
    if processResults(parseResults, "Parsing") > 0:
        exit(-1)

    logging.info ("Getting compile targets")
    compileTargets = json.loads(Path('testing/asn_compile_targets.json').read_text())
    logging.info (f"{len(compileTargets)} compile targets found")

    compileResults = compileAllTargets(compileTargets)
    if processResults(compileResults, "Compiling") > 0:
        exit(-1)