webwidgets 0.1.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- webwidgets/__init__.py +15 -0
- webwidgets/compilation/__init__.py +13 -0
- webwidgets/compilation/html/__init__.py +13 -0
- webwidgets/compilation/html/html_node.py +210 -0
- webwidgets/utility/__init__.py +13 -0
- webwidgets/utility/sanitizing.py +132 -0
- webwidgets-0.1.0.dist-info/METADATA +18 -0
- webwidgets-0.1.0.dist-info/RECORD +10 -0
- webwidgets-0.1.0.dist-info/WHEEL +4 -0
- webwidgets-0.1.0.dist-info/licenses/LICENSE +21 -0
webwidgets/__init__.py
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# =======================================================================
|
2
|
+
#
|
3
|
+
# This file is part of WebWidgets, a Python package for designing web
|
4
|
+
# UIs.
|
5
|
+
#
|
6
|
+
# You should have received a copy of the MIT License along with
|
7
|
+
# WebWidgets. If not, see <https://opensource.org/license/mit>.
|
8
|
+
#
|
9
|
+
# Copyright(C) 2025, mlaasri
|
10
|
+
#
|
11
|
+
# =======================================================================
|
12
|
+
|
13
|
+
__version__ = "0.1.0" # Dynamically set by build backend
|
14
|
+
|
15
|
+
from . import compilation
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# =======================================================================
|
2
|
+
#
|
3
|
+
# This file is part of WebWidgets, a Python package for designing web
|
4
|
+
# UIs.
|
5
|
+
#
|
6
|
+
# You should have received a copy of the MIT License along with
|
7
|
+
# WebWidgets. If not, see <https://opensource.org/license/mit>.
|
8
|
+
#
|
9
|
+
# Copyright(C) 2025, mlaasri
|
10
|
+
#
|
11
|
+
# =======================================================================
|
12
|
+
|
13
|
+
from . import html
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# =======================================================================
|
2
|
+
#
|
3
|
+
# This file is part of WebWidgets, a Python package for designing web
|
4
|
+
# UIs.
|
5
|
+
#
|
6
|
+
# You should have received a copy of the MIT License along with
|
7
|
+
# WebWidgets. If not, see <https://opensource.org/license/mit>.
|
8
|
+
#
|
9
|
+
# Copyright(C) 2025, mlaasri
|
10
|
+
#
|
11
|
+
# =======================================================================
|
12
|
+
|
13
|
+
from .html_node import HTMLNode, no_start_tag, no_end_tag, RawText
|
@@ -0,0 +1,210 @@
|
|
1
|
+
# =======================================================================
|
2
|
+
#
|
3
|
+
# This file is part of WebWidgets, a Python package for designing web
|
4
|
+
# UIs.
|
5
|
+
#
|
6
|
+
# You should have received a copy of the MIT License along with
|
7
|
+
# WebWidgets. If not, see <https://opensource.org/license/mit>.
|
8
|
+
#
|
9
|
+
# Copyright(C) 2025, mlaasri
|
10
|
+
#
|
11
|
+
# =======================================================================
|
12
|
+
|
13
|
+
import itertools
|
14
|
+
from typing import Any, Dict, List, Union
|
15
|
+
from webwidgets.utility.sanitizing import sanitize_html_text
|
16
|
+
|
17
|
+
|
18
|
+
class HTMLNode:
|
19
|
+
"""Represents an HTML node (for example, a div or a span).
|
20
|
+
"""
|
21
|
+
|
22
|
+
one_line: bool = False
|
23
|
+
|
24
|
+
def __init__(self, children: List['HTMLNode'] = [], attributes: Dict[str, str] = {}):
|
25
|
+
"""Creates an HTMLNode with optional children and attributes.
|
26
|
+
|
27
|
+
:param children: List of child HTML nodes. Defaults to an empty list.
|
28
|
+
:param attributes: Dictionary of attributes for the node. Defaults to an empty dictionary.
|
29
|
+
"""
|
30
|
+
self.children = children
|
31
|
+
self.attributes = attributes
|
32
|
+
|
33
|
+
def _get_tag_name(self) -> str:
|
34
|
+
"""Returns the tag name of the HTML node.
|
35
|
+
|
36
|
+
The tag name of a node object is the name of its class in lowercase.
|
37
|
+
|
38
|
+
:return: The tag name of the HTML node.
|
39
|
+
:rtype: str
|
40
|
+
"""
|
41
|
+
return self.__class__.__name__.lower()
|
42
|
+
|
43
|
+
def _render_attributes(self) -> str:
|
44
|
+
"""Renders the attributes of the HTML node into a string that can be added to the start tag.
|
45
|
+
|
46
|
+
:return: A string containing all attribute key-value pairs separated by spaces.
|
47
|
+
:rtype: str
|
48
|
+
"""
|
49
|
+
return ' '.join(
|
50
|
+
f'{key}="{value}"' for key, value in self.attributes.items()
|
51
|
+
)
|
52
|
+
|
53
|
+
def add(self, child: 'HTMLNode') -> None:
|
54
|
+
"""
|
55
|
+
Adds a child to the HTML node.
|
56
|
+
|
57
|
+
:param child: The child to be added.
|
58
|
+
"""
|
59
|
+
self.children.append(child)
|
60
|
+
|
61
|
+
@property
|
62
|
+
def start_tag(self) -> str:
|
63
|
+
"""Returns the opening tag of the HTML node, including any attributes.
|
64
|
+
|
65
|
+
:return: A string containing the opening tag of the element with its attributes.
|
66
|
+
:rtype: str
|
67
|
+
"""
|
68
|
+
# Rendering attributes
|
69
|
+
attributes = self._render_attributes()
|
70
|
+
maybe_space = ' ' if attributes else ''
|
71
|
+
|
72
|
+
# Building start tag
|
73
|
+
return f"<{self._get_tag_name()}{maybe_space}{attributes}>"
|
74
|
+
|
75
|
+
@property
|
76
|
+
def end_tag(self) -> str:
|
77
|
+
"""Returns the closing tag of the HTML node.
|
78
|
+
|
79
|
+
:return: A string containing the closing tag of the element.
|
80
|
+
:rtype: str
|
81
|
+
"""
|
82
|
+
return f"</{self._get_tag_name()}>"
|
83
|
+
|
84
|
+
def to_html(self, collapse_empty: bool = True,
|
85
|
+
indent_size: int = 4, indent_level: int = 0,
|
86
|
+
force_one_line: bool = False, return_lines: bool = False,
|
87
|
+
**kwargs: Any) -> Union[str, List[str]]:
|
88
|
+
"""Converts the HTML node into HTML code.
|
89
|
+
|
90
|
+
:param collapse_empty: If True, collapses empty elements into a single line.
|
91
|
+
Defaults to True.
|
92
|
+
:type collapse_empty: bool
|
93
|
+
:param indent_size: The number of spaces to use for each indentation level.
|
94
|
+
:type indent_size: int
|
95
|
+
:param indent_level: The current level of indentation in the HTML output.
|
96
|
+
:type indent_level: int
|
97
|
+
:param force_one_line: If True, forces all child elements to be rendered on a single line without additional
|
98
|
+
indentation. Defaults to False.
|
99
|
+
:type force_one_line: bool
|
100
|
+
:param return_lines: Whether to return the lines of HTML code individually. Defaults to False.
|
101
|
+
:type return_lines: bool
|
102
|
+
:param **kwargs: Additional keyword arguments to pass down to child elements.
|
103
|
+
:type **kwargs: Any
|
104
|
+
:return: A string containing the HTML representation of the element if
|
105
|
+
`return_lines` is `False` (default), or the list of individual lines
|
106
|
+
from that HTML code if `return_lines` is `True`.
|
107
|
+
:rtype: str or List[str]
|
108
|
+
"""
|
109
|
+
# Opening the element
|
110
|
+
indentation = "" if force_one_line else ' ' * indent_size * indent_level
|
111
|
+
html_lines = [indentation + self.start_tag]
|
112
|
+
|
113
|
+
# If content must be in one line
|
114
|
+
if self.one_line or force_one_line or (collapse_empty
|
115
|
+
and not self.children):
|
116
|
+
html_lines += list(itertools.chain.from_iterable(
|
117
|
+
[c.to_html(collapse_empty=collapse_empty,
|
118
|
+
indent_level=0, force_one_line=True, return_lines=True,
|
119
|
+
**kwargs)
|
120
|
+
for c in self.children]))
|
121
|
+
html_lines += [self.end_tag]
|
122
|
+
html_lines = [''.join(html_lines)] # Flattening the line
|
123
|
+
|
124
|
+
# If content spans multi-line
|
125
|
+
else:
|
126
|
+
html_lines += list(itertools.chain.from_iterable(
|
127
|
+
[c.to_html(collapse_empty=collapse_empty,
|
128
|
+
indent_size=indent_size,
|
129
|
+
indent_level=indent_level + 1,
|
130
|
+
return_lines=True,
|
131
|
+
**kwargs)
|
132
|
+
for c in self.children]))
|
133
|
+
html_lines += [indentation + self.end_tag]
|
134
|
+
html_lines = [l for l in html_lines if any(
|
135
|
+
c != ' ' for c in l)] # Trimming empty lines
|
136
|
+
|
137
|
+
# If return_lines is True, return a list of lines
|
138
|
+
if return_lines:
|
139
|
+
return html_lines
|
140
|
+
|
141
|
+
# Otherwise, return a single string
|
142
|
+
return '\n'.join(html_lines)
|
143
|
+
|
144
|
+
|
145
|
+
def no_start_tag(cls):
|
146
|
+
"""Decorator to remove the start tag from an HTMLNode subclass.
|
147
|
+
|
148
|
+
:param cls: A subclass of HTMLNode whose start tag should be removed.
|
149
|
+
:return: The given class with an empty start tag.
|
150
|
+
"""
|
151
|
+
cls.start_tag = property(
|
152
|
+
lambda _: '', doc="This element does not have a start tag")
|
153
|
+
return cls
|
154
|
+
|
155
|
+
|
156
|
+
def no_end_tag(cls):
|
157
|
+
"""Decorator to remove the end tag from an HTMLNode subclass.
|
158
|
+
|
159
|
+
:param cls: A subclass of HTMLNode whose end tag should be removed.
|
160
|
+
:return: The given class with an empty end tag.
|
161
|
+
"""
|
162
|
+
cls.end_tag = property(
|
163
|
+
lambda _: '', doc="This element does not have an end tag")
|
164
|
+
return cls
|
165
|
+
|
166
|
+
|
167
|
+
@no_start_tag
|
168
|
+
@no_end_tag
|
169
|
+
class RawText(HTMLNode):
|
170
|
+
"""A raw text node that contains text without any HTML tags."""
|
171
|
+
|
172
|
+
one_line = True
|
173
|
+
|
174
|
+
def __init__(self, text: str):
|
175
|
+
"""Creates a raw text node.
|
176
|
+
|
177
|
+
:param text: The text content of the node. It will be sanitized in
|
178
|
+
:py:meth:`RawText.to_html` before being written into HTML code.
|
179
|
+
:type text: str
|
180
|
+
"""
|
181
|
+
super().__init__()
|
182
|
+
self.text = text
|
183
|
+
|
184
|
+
def to_html(self, indent_size: int = 4, indent_level: int = 0,
|
185
|
+
return_lines: bool = False, replace_all_entities: bool = False,
|
186
|
+
**kwargs: Any) -> Union[str, List[str]]:
|
187
|
+
"""Converts the raw text node to HTML.
|
188
|
+
|
189
|
+
The text is sanitized by the :py:func:`sanitize_html_text` function before
|
190
|
+
being written into HTML code.
|
191
|
+
|
192
|
+
:param indent_size: See :py:meth:`HTMLNode.to_html`.
|
193
|
+
:type indent_size: int
|
194
|
+
:param indent_level: See :py:meth:`HTMLNode.to_html`.
|
195
|
+
:type indent_level: int
|
196
|
+
:param return_lines: See :py:meth:`HTMLNode.to_html`.
|
197
|
+
:type return_lines: bool
|
198
|
+
:param replace_all_entities: See :py:func:`sanitize_html_text`.
|
199
|
+
:type replace_all_entities: bool
|
200
|
+
:param kwargs: Other keyword arguments. These are ignored.
|
201
|
+
:type kwargs: Any
|
202
|
+
:return: See :py:meth:`HTMLNode.to_html`.
|
203
|
+
:rtype: str or List[str]
|
204
|
+
"""
|
205
|
+
sanitized = sanitize_html_text(
|
206
|
+
self.text, replace_all_entities=replace_all_entities)
|
207
|
+
line = ' ' * indent_size * indent_level + sanitized
|
208
|
+
if return_lines:
|
209
|
+
return [line]
|
210
|
+
return line
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# =======================================================================
|
2
|
+
#
|
3
|
+
# This file is part of WebWidgets, a Python package for designing web
|
4
|
+
# UIs.
|
5
|
+
#
|
6
|
+
# You should have received a copy of the MIT License along with
|
7
|
+
# WebWidgets. If not, see <https://opensource.org/license/mit>.
|
8
|
+
#
|
9
|
+
# Copyright(C) 2025, mlaasri
|
10
|
+
#
|
11
|
+
# =======================================================================
|
12
|
+
|
13
|
+
from .sanitizing import *
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# =======================================================================
|
2
|
+
#
|
3
|
+
# This file is part of WebWidgets, a Python package for designing web
|
4
|
+
# UIs.
|
5
|
+
#
|
6
|
+
# You should have received a copy of the MIT License along with
|
7
|
+
# WebWidgets. If not, see <https://opensource.org/license/mit>.
|
8
|
+
#
|
9
|
+
# Copyright(C) 2025, mlaasri
|
10
|
+
#
|
11
|
+
# =======================================================================
|
12
|
+
|
13
|
+
from html.entities import html5 as HTML_ENTITIES
|
14
|
+
import re
|
15
|
+
from typing import Tuple
|
16
|
+
|
17
|
+
|
18
|
+
# Maps characters to their corresponding character references. If a character can be
|
19
|
+
# represented by multiple entities, the preferred one is placed first in the tuple.
|
20
|
+
# Preference is given to the shortest one with a semicolon, in lowercase if possible
|
21
|
+
# (e.g. "&").
|
22
|
+
CHAR_TO_HTML_ENTITIES = {v: sorted([
|
23
|
+
k for k in HTML_ENTITIES if HTML_ENTITIES[k] == v
|
24
|
+
], key=len) for v in HTML_ENTITIES.values()}
|
25
|
+
for _, entities in CHAR_TO_HTML_ENTITIES.items():
|
26
|
+
e = next((e for e in entities if ';' in e), entities[0])
|
27
|
+
i = entities.index(e.lower() if e.lower() in entities else e)
|
28
|
+
entities[i], entities[0] = entities[0], entities[i]
|
29
|
+
CHAR_TO_HTML_ENTITIES = {k: tuple(v)
|
30
|
+
for k, v in CHAR_TO_HTML_ENTITIES.items()}
|
31
|
+
|
32
|
+
|
33
|
+
# Regular expression mathing all isolated '&' characters that are not part of an
|
34
|
+
# HTML entity.
|
35
|
+
_REGEX_AMP = re.compile(f"&(?!({'|'.join(HTML_ENTITIES.keys())}))")
|
36
|
+
|
37
|
+
|
38
|
+
# Regular expression matching all isolated ';' characters that are not part of an
|
39
|
+
# HTML entity. The expression essentially concatenates one lookbehind per entity.
|
40
|
+
_REGEXP_SEMI = re.compile(
|
41
|
+
''.join(f"(?<!&{e.replace(';', '')})"
|
42
|
+
for e in HTML_ENTITIES if ';' in e) + ';')
|
43
|
+
|
44
|
+
|
45
|
+
# Entities that are always replaced during sanitization. These are: <, >, /,
|
46
|
+
# according to rule 13.1.2.6 of the HTML5 specification, as well as single quotes
|
47
|
+
# ', double quotes ", and new line characters '\n'.
|
48
|
+
# Source: https://html.spec.whatwg.org/multipage/syntax.html#cdata-rcdata-restrictions
|
49
|
+
_ALWAYS_SANITIZED = ("\u003C", "\u003E", "\u002F", "'", "\"", "\n")
|
50
|
+
|
51
|
+
|
52
|
+
# Entities other than new line characters '\n' (which require special treatment)
|
53
|
+
# that are always replaced during sanitization.
|
54
|
+
_ALWAYS_SANITIZED_BUT_NEW_LINES = tuple(
|
55
|
+
e for e in _ALWAYS_SANITIZED if e != '\n')
|
56
|
+
|
57
|
+
|
58
|
+
# Entities other than the ampersand and semicolon (which require special treatment
|
59
|
+
# because they are part of other entities) that are replaced by default during
|
60
|
+
# sanitization but can also be skipped for speed. This set of entities consists of
|
61
|
+
# all remaining entities but the ampersand and semicolon.
|
62
|
+
_OPTIONALLY_SANITIZED_BUT_AMP_SEMI = tuple(
|
63
|
+
set(CHAR_TO_HTML_ENTITIES.keys()) - set(_ALWAYS_SANITIZED) - set({'&', ';'}))
|
64
|
+
|
65
|
+
|
66
|
+
def replace_html_entities(text: str, characters: Tuple[str]) -> str:
|
67
|
+
"""Replaces characters with their corresponding HTML entities in the given text.
|
68
|
+
|
69
|
+
If a character can be represented by multiple entities, preference is given to
|
70
|
+
the shortest one that contains a semicolon, in lowercase if possible.
|
71
|
+
|
72
|
+
:param text: The input text containing HTML entities.
|
73
|
+
:type text: str
|
74
|
+
:param characters: The characters to be replaced by their HTML entity. Usually
|
75
|
+
each item in the tuple is a single character, but some entities span
|
76
|
+
multiple characters.
|
77
|
+
:type characters: Tuple[str]
|
78
|
+
:return: The text with HTML entities replaced.
|
79
|
+
:rtype: str
|
80
|
+
"""
|
81
|
+
for c in characters:
|
82
|
+
entity = CHAR_TO_HTML_ENTITIES[c][0] # Preferred is first
|
83
|
+
text = text.replace(c, '&' + entity)
|
84
|
+
return text
|
85
|
+
|
86
|
+
|
87
|
+
def sanitize_html_text(text: str, replace_all_entities: bool = False) -> str:
|
88
|
+
"""Sanitizes raw HTML text by replacing certain characters with HTML-friendly equivalents.
|
89
|
+
|
90
|
+
Sanitization affects the following characters:
|
91
|
+
- `<`, `/`, and `>`, replaced with their corresponding HTML entities `lt;`,
|
92
|
+
`gt;`, and `sol;` according to rule 13.1.2.6 of the HTML5 specification
|
93
|
+
(see source:
|
94
|
+
https://html.spec.whatwg.org/multipage/syntax.html#cdata-rcdata-restrictions)
|
95
|
+
- single quotes `'` and double quotes `"`, replaced with their corresponding
|
96
|
+
HTML entities `apos;` and `quot;`
|
97
|
+
- new line characters '\\n', replaced with `br` tags
|
98
|
+
- if `replace_all_entities` is True, every character that can be represented by
|
99
|
+
an HTML entity is replaced with that entity. If a character can be
|
100
|
+
represented by multiple entities, preference is given to the shortest one
|
101
|
+
that contains a semicolon, in lowercase if possible.
|
102
|
+
|
103
|
+
See https://html.spec.whatwg.org/multipage/named-characters.html for a list of
|
104
|
+
all supported entities.
|
105
|
+
|
106
|
+
:param text: The raw HTML text that needs sanitization.
|
107
|
+
:type text: str
|
108
|
+
:param replace_all_entities: Whether to replace every character that can be
|
109
|
+
represented by an HTML entity. Use False to skip non-mandatory characters
|
110
|
+
and increase speed. Default is False.
|
111
|
+
:type replace_all_entities: bool
|
112
|
+
:return: The sanitized HTML text.
|
113
|
+
:rtype: str
|
114
|
+
"""
|
115
|
+
# We start with all optional HTML entities, which enables us to replace all '&'
|
116
|
+
# and ';' before subsequently introducing more of them.
|
117
|
+
if replace_all_entities:
|
118
|
+
|
119
|
+
# Replacing '&' ONLY when not part of an HTML entity itself
|
120
|
+
text = _REGEX_AMP.sub('&', text)
|
121
|
+
|
122
|
+
# Replacing ';' ONLY when not part of an HTML entity itself
|
123
|
+
text = _REGEXP_SEMI.sub(';', text)
|
124
|
+
|
125
|
+
# Replacing the remaining HTML entities
|
126
|
+
text = replace_html_entities(text, _OPTIONALLY_SANITIZED_BUT_AMP_SEMI)
|
127
|
+
|
128
|
+
# Then we replace all mandatory HTML entities
|
129
|
+
text = replace_html_entities(text, _ALWAYS_SANITIZED_BUT_NEW_LINES)
|
130
|
+
text = text.replace('\n', '<br>') # Has to be last because of < and >
|
131
|
+
|
132
|
+
return text
|
@@ -0,0 +1,18 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: webwidgets
|
3
|
+
Version: 0.1.0
|
4
|
+
Summary: A Python package for designing web UIs.
|
5
|
+
Project-URL: Source code, https://github.com/mlaasri/WebWidgets
|
6
|
+
Author: mlaasri
|
7
|
+
License-File: LICENSE
|
8
|
+
Keywords: design,webui
|
9
|
+
Classifier: Operating System :: OS Independent
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
11
|
+
Requires-Python: >=3.9
|
12
|
+
Description-Content-Type: text/markdown
|
13
|
+
|
14
|
+
# WebWidgets
|
15
|
+
|
16
|
+

|
17
|
+
|
18
|
+
A Python package for creating web UIs
|
@@ -0,0 +1,10 @@
|
|
1
|
+
webwidgets/__init__.py,sha256=V5LcquUtoHyCfL072Oo3HNH73msF9lKd4Vx5odghyZk,481
|
2
|
+
webwidgets/compilation/__init__.py,sha256=rebNuvAfuSVcRiuMOezktEod6C7qKl9tp__ddV4PNMg,415
|
3
|
+
webwidgets/compilation/html/__init__.py,sha256=t7xIAup9slzxfyp-spbXFM8YxPBJF0JJyaAmMe5NmXI,463
|
4
|
+
webwidgets/compilation/html/html_node.py,sha256=R4SsjvmbiM4WvcVuwhyXLwxYqdk9MoqVnr4idqFpZ0s,7846
|
5
|
+
webwidgets/utility/__init__.py,sha256=6lYOxZIb_wcNGpu55FjPN9fTben37x8I2XU6HOPmADo,422
|
6
|
+
webwidgets/utility/sanitizing.py,sha256=L72-E6er9p5e3nLupJJqcMMB62O_4lYzaDyMEyhBjE4,5768
|
7
|
+
webwidgets-0.1.0.dist-info/METADATA,sha256=NgmpZVNES4wgiMZSIWqE1CKDtPy3YRrnlIe7DV0dcH0,550
|
8
|
+
webwidgets-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
9
|
+
webwidgets-0.1.0.dist-info/licenses/LICENSE,sha256=LISw1mw5eK6i8adFSlx6zltZxrJFwurngVdZAEU8g_I,1064
|
10
|
+
webwidgets-0.1.0.dist-info/RECORD,,
|
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 mlaasri
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|