1#
2# Copyright (c), 2018-2020, SISSA (International School for Advanced Studies).
3# All rights reserved.
4# This file is distributed under the terms of the MIT License.
5# See the file 'LICENSE' in the root directory of the present
6# distribution, or http://opensource.org/licenses/MIT.
7#
8# @author Davide Brunato <brunato@sissa.it>
9#
10import locale
11from typing import Optional, Any
12from .tdop import Token
13
14
15class ElementPathError(Exception):
16    """
17    Base exception class for elementpath package.
18
19    :param message: the message related to the error.
20    :param code: an optional error code.
21    :param token: an optional token instance related with the error.
22    """
23    def __init__(self, message: str,
24                 code: Optional[str] = None,
25                 token: Optional[Token[Any]] = None) -> None:
26        super(ElementPathError, self).__init__(message)
27        self.message = message
28        self.code = code
29        self.token = token
30
31    def __str__(self) -> str:
32        if self.token is None or not isinstance(self.token.value, (str, bytes)):
33            if not self.code:
34                return self.message
35            return '[{}] {}'.format(self.code, self.message)
36        elif not self.code:
37            return '{1} at line {2}, column {3}: {0}'.format(
38                self.message, self.token, *self.token.position
39            )
40        return '{2} at line {3}, column {4}: [{1}] {0}'.format(
41            self.message, self.code, self.token, *self.token.position
42        )
43
44
45class MissingContextError(ElementPathError):
46    """Raised when the dynamic context is required for evaluate the XPath expression."""
47
48
49class ElementPathKeyError(ElementPathError, KeyError):
50    pass
51
52
53class ElementPathZeroDivisionError(ElementPathError, ZeroDivisionError):
54    pass
55
56
57class ElementPathNameError(ElementPathError, NameError):
58    pass
59
60
61class ElementPathOverflowError(ElementPathError, OverflowError):
62    pass
63
64
65class ElementPathRuntimeError(ElementPathError, RuntimeError):
66    pass
67
68
69class ElementPathSyntaxError(ElementPathError, SyntaxError):
70    pass
71
72
73class ElementPathTypeError(ElementPathError, TypeError):
74    pass
75
76
77class ElementPathValueError(ElementPathError, ValueError):
78    pass
79
80
81class ElementPathLocaleError(ElementPathError, locale.Error):
82    pass
83
84
85XPATH_ERROR_CODES = {
86    # XPath 2.0 parser error (https://www.w3.org/TR/xpath20/#id-errors)
87    'XPST0001': (ElementPathValueError, 'Parser not bound to a schema'),
88    'XPST0003': (ElementPathSyntaxError, 'Invalid XPath expression'),
89    'XPDY0002': (MissingContextError, 'Dynamic context required for evaluate'),
90    'XPTY0004': (ElementPathTypeError, 'Type is not appropriate for the context'),
91    'XPST0005': (ElementPathValueError, 'A not empty sequence required'),
92    'XPST0008': (ElementPathNameError, 'Name not found'),
93    'XPST0010': (ElementPathNameError, 'Axis not found'),
94    'XPST0017': (ElementPathTypeError, 'Wrong number of arguments'),
95    'XPTY0018': (ElementPathTypeError,
96                 'Step result contains both nodes and atomic values'),
97    'XPTY0019': (ElementPathTypeError, 'Intermediate step contains an atomic value'),
98    'XPTY0020': (ElementPathTypeError, 'Context item is not a node'),
99    'XPDY0050': (ElementPathTypeError, 'Type does not match sequence type'),
100    'XPST0051': (ElementPathNameError, 'Unknown atomic type'),
101    'XPST0080': (ElementPathNameError,
102                 'Target type cannot be xs:NOTATION or xs:anyAtomicType'),
103    'XPST0081': (ElementPathNameError, 'Unknown namespace'),
104
105    # XPath data types and function errors
106    'FOER0000': (ElementPathError, 'Unidentified error'),
107    'FOAR0001': (ElementPathZeroDivisionError, 'Division by zero'),
108    'FOAR0002': (ElementPathOverflowError, 'Numeric operation overflow/underflow'),
109    'FOCA0001': (ElementPathValueError, 'Input value too large for decimal'),
110    'FOCA0002': (ElementPathValueError, 'Invalid lexical value'),
111    'FOCA0003': (ElementPathValueError, 'Input value too large for integer'),
112    'FOCA0005': (ElementPathValueError, 'NaN supplied as float/double value'),
113    'FOCA0006': (ElementPathValueError,
114                 'String to be cast to decimal has too many digits of precision'),
115    'FOCH0001': (ElementPathValueError, 'Code point not valid'),
116    'FOCH0002': (ElementPathLocaleError, 'Unsupported collation'),
117    'FOCH0003': (ElementPathValueError, 'Unsupported normalization form'),
118    'FOCH0004': (ElementPathValueError, 'Collation does not support collation units'),
119    'FODC0001': (ElementPathValueError, 'No context document'),
120    'FODC0002': (ElementPathValueError, 'Error retrieving resource'),
121    'FODC0003': (ElementPathValueError, 'Function stability not defined'),
122    'FODC0004': (ElementPathValueError, 'Invalid argument to fn:collection'),
123    'FODC0005': (ElementPathValueError, 'Invalid argument to fn:doc or fn:doc-available'),
124    'FODT0001': (ElementPathOverflowError, 'Overflow/underflow in date/time operation'),
125    'FODT0002': (ElementPathOverflowError, 'Overflow/underflow in duration operation'),
126    'FODT0003': (ElementPathValueError, 'Invalid timezone value'),
127    'FONS0004': (ElementPathKeyError, 'No namespace found for prefix'),
128    'FONS0005': (ElementPathValueError, 'Base-uri not defined in the static context'),
129    'FORG0001': (ElementPathValueError, 'Invalid value for cast/constructor'),
130    'FORG0002': (ElementPathValueError, 'Invalid argument to fn:resolve-uri()'),
131    'FORG0003': (ElementPathValueError,
132                 'fn:zero-or-one called with a sequence containing more than one item'),
133    'FORG0004': (ElementPathValueError,
134                 'fn:one-or-more called with a sequence containing no items'),
135    'FORG0005': (ElementPathValueError,
136                 'fn:exactly-one called with a sequence containing zero or more than one item'),
137    'FORG0006': (ElementPathTypeError, 'Invalid argument type'),
138    'FORG0008': (ElementPathValueError,
139                 'The two arguments to fn:dateTime have inconsistent timezones'),
140    'FORG0009': (ElementPathValueError,
141                 'Error in resolving a relative URI against a base URI in fn:resolve-uri'),
142    'FORX0001': (ElementPathValueError, 'Invalid regular expression flags'),
143    'FORX0002': (ElementPathValueError, 'Invalid regular expression'),
144    'FORX0003': (ElementPathValueError, 'Regular expression matches zero-length string'),
145    'FORX0004': (ElementPathValueError, 'Invalid replacement string'),
146    'FOTY0012': (ElementPathValueError, 'Argument node does not have a typed value'),
147
148    # XPath 3.0 errors
149    'FOTY0013': (ElementPathTypeError, 'The argument to fn:data() contains a function item'),
150    'FOTY0014': (ElementPathTypeError, 'The argument to fn:string() is a function item'),
151    'FOTY0015': (ElementPathTypeError,
152                 'An argument to fn:deep-equal() contains a function item'),
153    'FODC0006': (ElementPathValueError,
154                 'String passed to fn:parse-xml is not a well-formed XML document'),
155    'FODC0010': (ElementPathRuntimeError,
156                 'The processor does not support serialization'),
157    'FOUT1170': (ElementPathValueError, 'Invalid $href argument to fn:unparsed-text()'),
158    'FOUT1190': (ElementPathValueError,
159                 'Cannot decode resource retrieved by fn:unparsed-text()'),
160    'FOUT1200': (ElementPathValueError,
161                 'Cannot infer encoding of resource retrieved by fn:unparsed-text()'),
162    'FODF1280': (ElementPathValueError, 'Invalid decimal format name'),
163    'FODF1310': (ElementPathValueError, 'Invalid decimal format picture string'),
164    'FOFD1340': (ElementPathValueError, 'Invalid date/time formatting parameters'),
165    'FOFD1350': (ElementPathValueError, 'Invalid date/time formatting component'),
166
167    # XSLT and XQuery Serialization errors
168    # (the complete list: https://www.w3.org/TR/xslt-xquery-serialization/#id-errors)
169    'SENR0001': (ElementPathTypeError, 'item is an attribute node or a namespace node'),
170    'SEPM0016': (ElementPathValueError, 'parameter value is invalid for the defined domain'),
171    'SEPM0017': (ElementPathValueError, 'error during extraction of serialization parameters'),
172    'SEPM0018': (ElementPathValueError, 'use-character-maps serialization parameter in '
173                                        'a sequence of length greater than one'),
174    'SEPM0019': (ElementPathValueError, 'same serialization parameter appears more than once'),
175}
176
177
178def xpath_error(code: str, message: Optional[str] = None,
179                token: Optional[Token[Any]] = None, prefix: str = 'err') -> ElementPathError:
180    """
181    Returns an XPath error instance related with a code. An XPath/XQuery/XSLT error code
182    (ref: http://www.w3.org/2005/xqt-errors) is an alphanumeric token starting with four
183    uppercase letters and ending with four digits.
184
185    :param code: the error code.
186    :param message: an optional custom additional message.
187    :param token: an optional token instance.
188    :param prefix: the namespace prefix to apply to the error code, defaults to 'err'.
189    """
190    if code.startswith('{'):
191        try:
192            namespace, code = code[1:].split('}')
193        except ValueError:
194            message = '{!r} is not an xs:QName'.format(code)
195            raise ElementPathValueError(message, 'err:XPTY0004', token)
196        else:
197            if namespace != 'http://www.w3.org/2005/xqt-errors':
198                message = 'invalid namespace {!r}'.format(namespace)
199                raise ElementPathValueError(message, 'err:XPTY0004', token)
200            pcode = '%s:%s' % (prefix, code) if prefix else code
201    elif ':' not in code:
202        pcode = '%s:%s' % (prefix, code) if prefix else code
203    elif not prefix or not code.startswith(prefix + ':'):
204        message = '%r is not an XPath error code' % code
205        raise ElementPathValueError(message, 'err:XPTY0004', token)
206    else:
207        pcode = code
208        code = code[len(prefix) + 1:]
209
210    try:
211        error_class, default_message = XPATH_ERROR_CODES[code]
212    except KeyError:
213        raise ElementPathValueError(
214            message or 'unknown XPath error code %r' % code, 'err:XPTY0004', token
215        )
216    else:
217        return error_class(message or default_message, pcode, token)
218