1# Licensed under a 3-clause BSD style license - see LICENSE.rst
2"""
3Various XML-related utilities
4"""
5
6
7# ASTROPY
8from astropy.logger import log
9from astropy.utils import data
10from astropy.utils.xml import check as xml_check
11from astropy.utils.xml import validate
12
13# LOCAL
14from .exceptions import (warn_or_raise, vo_warn, W02, W03, W04, W05)
15
16
17__all__ = [
18    'check_id', 'fix_id', 'check_token', 'check_mime_content_type',
19    'check_anyuri', 'validate_schema'
20    ]
21
22
23def check_id(ID, name='ID', config=None, pos=None):
24    """
25    Raises a `~astropy.io.votable.exceptions.VOTableSpecError` if *ID*
26    is not a valid XML ID_.
27
28    *name* is the name of the attribute being checked (used only for
29    error messages).
30    """
31    if (ID is not None and not xml_check.check_id(ID)):
32        warn_or_raise(W02, W02, (name, ID), config, pos)
33        return False
34    return True
35
36
37def fix_id(ID, config=None, pos=None):
38    """
39    Given an arbitrary string, create one that can be used as an xml id.
40
41    This is rather simplistic at the moment, since it just replaces
42    non-valid characters with underscores.
43    """
44    if ID is None:
45        return None
46    corrected = xml_check.fix_id(ID)
47    if corrected != ID:
48        vo_warn(W03, (ID, corrected), config, pos)
49    return corrected
50
51
52_token_regex = r"(?![\r\l\t ])[^\r\l\t]*(?![\r\l\t ])"
53
54
55def check_token(token, attr_name, config=None, pos=None):
56    """
57    Raises a `ValueError` if *token* is not a valid XML token.
58
59    As defined by XML Schema Part 2.
60    """
61    if (token is not None and not xml_check.check_token(token)):
62        return False
63    return True
64
65
66def check_mime_content_type(content_type, config=None, pos=None):
67    """
68    Raises a `~astropy.io.votable.exceptions.VOTableSpecError` if
69    *content_type* is not a valid MIME content type.
70
71    As defined by RFC 2045 (syntactically, at least).
72    """
73    if (content_type is not None and
74        not xml_check.check_mime_content_type(content_type)):
75        warn_or_raise(W04, W04, content_type, config, pos)
76        return False
77    return True
78
79
80def check_anyuri(uri, config=None, pos=None):
81    """
82    Raises a `~astropy.io.votable.exceptions.VOTableSpecError` if
83    *uri* is not a valid URI.
84
85    As defined in RFC 2396.
86    """
87    if (uri is not None and not xml_check.check_anyuri(uri)):
88        warn_or_raise(W05, W05, uri, config, pos)
89        return False
90    return True
91
92
93def validate_schema(filename, version='1.1'):
94    """
95    Validates the given file against the appropriate VOTable schema.
96
97    Parameters
98    ----------
99    filename : str
100        The path to the XML file to validate
101
102    version : str, optional
103        The VOTABLE version to check, which must be a string \"1.0\",
104        \"1.1\", \"1.2\" or \"1.3\".  If it is not one of these,
105        version \"1.1\" is assumed.
106
107        For version \"1.0\", it is checked against a DTD, since that
108        version did not have an XML Schema.
109
110    Returns
111    -------
112    returncode, stdout, stderr : int, str, str
113        Returns the returncode from xmllint and the stdout and stderr
114        as strings
115    """
116    if version not in ('1.0', '1.1', '1.2', '1.3'):
117        log.info(f'{filename} has version {version}, using schema 1.1')
118        version = '1.1'
119
120    if version in ('1.1', '1.2', '1.3'):
121        schema_path = data.get_pkg_data_filename(
122            f'data/VOTable.v{version}.xsd')
123    else:
124        schema_path = data.get_pkg_data_filename(
125            'data/VOTable.dtd')
126
127    return validate.validate_schema(filename, schema_path)
128