diff --git a/docs/source/about/changelog.rst b/docs/source/about/changelog.rst index 3db857a26..6319a3b63 100644 --- a/docs/source/about/changelog.rst +++ b/docs/source/about/changelog.rst @@ -23,21 +23,23 @@ more info, see the :ref:`Contributor Guide `. Unreleased ---------- -**Removed** - -- :pull:`840` - Remove ``IDOM_FEATURE_INDEX_AS_DEFAULT_KEY`` option -- :pull:`835` - ``serve_static_files`` option from backend configuration - **Added** - :pull:`835` - Ability to customize the ```` element of IDOM's built-in client. - :pull:`835` - ``vdom_to_html`` utility function. - :pull:`843` - Ability to subscribe to changes that are made to mutable options. +- :pull:`832` - ``del_html_head_body_transform`` to remove ````, ````, and ```` while preserving children. - :pull:`699` - Support for form element serialization **Fixed** - :issue:`582` - ``IDOM_DEBUG_MODE`` is now mutable and can be changed at runtime +- :pull:`832` - Fix ``html_to_vdom`` improperly removing ````, ````, and ```` nodes. + +**Removed** +- :pull:`832` - Removed ``idom.html.body`` as it is currently unusable due to technological limitations, and thus not needed. +- :pull:`840` - remove ``IDOM_FEATURE_INDEX_AS_DEFAULT_KEY`` option +- :pull:`835` - ``serve_static_files`` option from backend configuration v0.41.0 diff --git a/src/idom/html.py b/src/idom/html.py index 45db97463..7421e2f2a 100644 --- a/src/idom/html.py +++ b/src/idom/html.py @@ -14,7 +14,6 @@ **Content sectioning** -- :func:`body` - :func:`address` - :func:`article` - :func:`aside` @@ -189,7 +188,6 @@ def _(*children: Any, key: Key | None = None) -> VdomDict: title = make_vdom_constructor("title") # Content sectioning -body = make_vdom_constructor("body") address = make_vdom_constructor("address") article = make_vdom_constructor("article") aside = make_vdom_constructor("aside") diff --git a/src/idom/utils.py b/src/idom/utils.py index fde800abb..1deab122d 100644 --- a/src/idom/utils.py +++ b/src/idom/utils.py @@ -5,7 +5,7 @@ from typing import Any, Callable, Generic, Iterable, TypeVar, cast from lxml import etree -from lxml.html import fragments_fromstring, tostring +from lxml.html import fromstring, tostring import idom from idom.core.types import VdomDict @@ -85,7 +85,7 @@ def html_to_vdom( using a ``key=...`` attribute within your HTML tag. Parameters: - source: + html: The raw HTML as a string transforms: Functions of the form ``transform(old) -> new`` where ``old`` is a VDOM @@ -99,15 +99,15 @@ def html_to_vdom( raise TypeError(f"Expected html to be a string, not {type(html).__name__}") # If the user provided a string, convert it to a list of lxml.etree nodes - parser = etree.HTMLParser( - remove_comments=True, - remove_pis=True, - remove_blank_text=True, - recover=not strict, - ) try: - nodes: list[etree._Element] = fragments_fromstring( - html, no_leading_text=True, parser=parser + root_node: etree._Element = fromstring( + html.strip(), + parser=etree.HTMLParser( + remove_comments=True, + remove_pis=True, + remove_blank_text=True, + recover=not strict, + ), ) except etree.XMLSyntaxError as e: if not strict: @@ -119,25 +119,8 @@ def html_to_vdom( "you can disable the strict parameter on html_to_vdom().\n" "Otherwise, repair your broken HTML and try again." ) from e - has_root_node = len(nodes) == 1 - - # Find or create a root node - if has_root_node: - root_node = nodes[0] - else: - # etree.Element requires a non-empty tag - we correct this below - root_node = etree.Element("TEMP", None, None) - for child in nodes: - root_node.append(child) - # Convert the lxml node to a VDOM dict - vdom = _etree_to_vdom(root_node, transforms) - - # Change the artificially created root node to a React Fragment, instead of a div - if not has_root_node: - vdom["tagName"] = "" - - return vdom + return _etree_to_vdom(root_node, transforms) class HTMLParseError(etree.LxmlSyntaxError): # type: ignore[misc] @@ -147,10 +130,10 @@ class HTMLParseError(etree.LxmlSyntaxError): # type: ignore[misc] def _etree_to_vdom( node: etree._Element, transforms: Iterable[_ModelTransform] ) -> VdomDict: - """Recusively transform an lxml etree node into a DOM model + """Transform an lxml etree node into a DOM model Parameters: - source: + node: The ``lxml.etree._Element`` node transforms: Functions of the form ``transform(old) -> new`` where ``old`` is a VDOM @@ -162,7 +145,7 @@ def _etree_to_vdom( f"Expected node to be a etree._Element, not {type(node).__name__}" ) - # This will recursively call _etree_to_vdom() on all children + # Recursively call _etree_to_vdom() on all children children = _generate_vdom_children(node, transforms) # Convert the lxml node to a VDOM dict @@ -289,6 +272,20 @@ def _hypen_to_camel_case(string: str) -> str: return first.lower() + remainder.title().replace("-", "") +def del_html_head_body_transform(vdom: VdomDict) -> VdomDict: + """Transform intended for use with `html_to_vdom`. + + Removes ``, ``, and `` while preserving their children. + + Parameters: + vdom: + The VDOM dictionary to transform. + """ + if vdom["tagName"] in {"html", "body", "head"}: + return {"tagName": "", "children": vdom["children"]} + return vdom + + def _vdom_attr_to_html_str(key: str, value: Any) -> tuple[str, str]: if key == "style": if isinstance(value, dict): diff --git a/tests/test_utils.py b/tests/test_utils.py index 405cdce05..f7518b014 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -4,7 +4,12 @@ import idom from idom import html -from idom.utils import HTMLParseError, html_to_vdom, vdom_to_html +from idom.utils import ( + HTMLParseError, + del_html_head_body_transform, + html_to_vdom, + vdom_to_html, +) def test_basic_ref_behavior(): @@ -144,7 +149,7 @@ def test_html_to_vdom_with_no_parent_node(): source = "

Hello

World
" expected = { - "tagName": "", + "tagName": "div", "children": [ {"tagName": "p", "children": ["Hello"]}, {"tagName": "div", "children": ["World"]}, @@ -154,6 +159,37 @@ def test_html_to_vdom_with_no_parent_node(): assert html_to_vdom(source) == expected +def test_del_html_body_transform(): + source = """ + + + + + My Title + + +

Hello World

+ + + """ + + expected = { + "tagName": "", + "children": [ + { + "tagName": "", + "children": [{"tagName": "title", "children": ["My Title"]}], + }, + { + "tagName": "", + "children": [{"tagName": "h1", "children": ["Hello World"]}], + }, + ], + } + + assert html_to_vdom(source, del_html_head_body_transform) == expected + + SOME_OBJECT = object()