Skip to content

Commit 81f86a2

Browse files
committed
Allow empty attribute values. Fixes #165.
1 parent aadb3da commit 81f86a2

5 files changed

Lines changed: 41 additions & 19 deletions

File tree

CHANGES.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ selectolax Changelog
55
- Improve type hints, add docstrings to type hints
66
- Prevent decomposing of the root node
77
- Unpin Cython version and make it Optional
8-
8+
- Allow empty attribute values. Fixes #165.
99

1010
Version 0.3.30
1111
-------------

selectolax/lexbor.pxd

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,21 +60,21 @@ cdef extern from "lexbor/html/html.h" nogil:
6060

6161
ctypedef struct lxb_dom_node_t:
6262
lxb_dom_event_target_t event_target
63-
64-
63+
64+
6565
uintptr_t local_name
6666
uintptr_t prefix
6767
uintptr_t ns
68-
68+
6969
lxb_dom_document_t *owner_document
70-
70+
7171
lxb_dom_node_t *next
7272
lxb_dom_node_t *prev
7373
lxb_dom_node_t *parent
7474
lxb_dom_node_t *first_child
7575
lxb_dom_node_t *last_child
7676
void *user
77-
77+
7878
lxb_dom_node_type_t type
7979

8080

@@ -318,7 +318,6 @@ cdef extern from "lexbor/dom/dom.h" nogil:
318318
void lxb_dom_node_insert_child(lxb_dom_node_t *to, lxb_dom_node_t *node)
319319
void lxb_dom_node_insert_before(lxb_dom_node_t *to, lxb_dom_node_t *node)
320320
void lxb_dom_node_insert_after(lxb_dom_node_t *to, lxb_dom_node_t *node)
321-
322321
lxb_dom_text_t * lxb_dom_document_create_text_node(lxb_dom_document_t *document, const lxb_char_t *data, size_t len)
323322
void lxb_dom_node_simple_walk(lxb_dom_node_t *root, lxb_dom_node_simple_walker_f walker_cb, void *ctx)
324323

selectolax/lexbor.pyi

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any, Iterator, Literal, TypeVar, NoReturn, overload
1+
from typing import Any, Iterator, Literal, TypeVar, NoReturn, overload, Optional
22

33
DefaultT = TypeVar("DefaultT")
44

@@ -13,7 +13,7 @@ class LexborAttributes:
1313
def __iter__(self) -> Iterator[str]: ...
1414
def __len__(self) -> int: ...
1515
def __getitem__(self, key: str) -> str | None: ...
16-
def __setitem__(self, key: str, value: str) -> None: ...
16+
def __setitem__(self, key: str, value: Optional[str]) -> None: ...
1717
def __delitem__(self, key: str) -> None: ...
1818
def __contains__(self, key: str) -> bool: ...
1919
def __repr__(self) -> str: ...

selectolax/lexbor/attrs.pxi

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
cimport cython
22

3+
from typing import Optional
4+
35
@cython.final
46
cdef class LexborAttributes:
57
"""A dict-like object that represents attributes."""
@@ -23,16 +25,32 @@ cdef class LexborAttributes:
2325
yield key.decode(_ENCODING)
2426
attr = attr.next
2527

26-
def __setitem__(self, str key, value):
27-
value = str(value)
28+
def __setitem__(self, str key, object value):
29+
value = value
2830
bytes_key = key.encode(_ENCODING)
29-
bytes_value = value.encode(_ENCODING)
30-
31-
lxb_dom_element_set_attribute(
32-
<lxb_dom_element_t *> self.node,
33-
<lxb_char_t *> bytes_key, len(bytes_key),
34-
<lxb_char_t *> bytes_value, len(bytes_value),
35-
)
31+
bytes_value = value.encode(_ENCODING) if value else b""
32+
cdef lxb_dom_attr_t *attr
33+
cdef lxb_dom_document_t *doc
34+
35+
if value is None:
36+
# N.B. This is suboptimal, but there is not API to set empty attributes
37+
attr = lxb_dom_element_set_attribute(
38+
<lxb_dom_element_t *> self.node,
39+
<lxb_char_t *> bytes_key, len(bytes_key),
40+
NULL, 0
41+
)
42+
doc = (<lxb_dom_node_t*>attr).owner_document
43+
lexbor_str_destroy(attr.value, doc.text, 0)
44+
attr.value = NULL
45+
46+
elif isinstance(value, str) or isinstance(value, unicode) :
47+
lxb_dom_element_set_attribute(
48+
<lxb_dom_element_t *> self.node,
49+
<lxb_char_t *> bytes_key, len(bytes_key),
50+
<lxb_char_t *> bytes_value, len(bytes_value),
51+
)
52+
else:
53+
raise TypeError("Expected str or unicode, got %s" % type(value))
3654

3755
def __delitem__(self, key):
3856
try:

tests/test_parser.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import pytest
55
from selectolax.parser import HTMLParser, Node
66

7-
from selectolax.lexbor import LexborHTMLParser, LexborNode, SelectolaxError
7+
from selectolax.lexbor import LexborHTMLParser, LexborNode, SelectolaxError, create_tag
88

99
"""
1010
We'are testing only our own code.
@@ -303,3 +303,8 @@ def test_decompose_root_node():
303303
html_parser = LexborHTMLParser("<div><p>test</p></div>")
304304
with pytest.raises(SelectolaxError):
305305
html_parser.root.decompose()
306+
307+
def test_empty_attribute_lexbor():
308+
div = create_tag("div")
309+
div.attrs["hidden"] = None
310+
assert div.html == "<div hidden></div>"

0 commit comments

Comments
 (0)