collective.html2blocks 1.0.0a1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. collective.html2blocks-1.0.0a1/.gitignore +18 -0
  2. collective.html2blocks-1.0.0a1/PKG-INFO +47 -0
  3. collective.html2blocks-1.0.0a1/README.md +20 -0
  4. collective.html2blocks-1.0.0a1/collective/html2blocks/__init__.py +2 -0
  5. collective.html2blocks-1.0.0a1/collective/html2blocks/_types.py +119 -0
  6. collective.html2blocks-1.0.0a1/collective/html2blocks/blocks/__init__.py +56 -0
  7. collective.html2blocks-1.0.0a1/collective/html2blocks/blocks/iframe/__init__.py +31 -0
  8. collective.html2blocks-1.0.0a1/collective/html2blocks/blocks/iframe/soundcloud.py +25 -0
  9. collective.html2blocks-1.0.0a1/collective/html2blocks/blocks/iframe/youtube.py +32 -0
  10. collective.html2blocks-1.0.0a1/collective/html2blocks/blocks/image.py +112 -0
  11. collective.html2blocks-1.0.0a1/collective/html2blocks/blocks/slate/__init__.py +69 -0
  12. collective.html2blocks-1.0.0a1/collective/html2blocks/blocks/slate/parser.py +530 -0
  13. collective.html2blocks-1.0.0a1/collective/html2blocks/blocks/table.py +136 -0
  14. collective.html2blocks-1.0.0a1/collective/html2blocks/blocks/video.py +50 -0
  15. collective.html2blocks-1.0.0a1/collective/html2blocks/cli.py +62 -0
  16. collective.html2blocks-1.0.0a1/collective/html2blocks/commands/__init__.py +4 -0
  17. collective.html2blocks-1.0.0a1/collective/html2blocks/commands/convert.py +95 -0
  18. collective.html2blocks-1.0.0a1/collective/html2blocks/commands/info.py +53 -0
  19. collective.html2blocks-1.0.0a1/collective/html2blocks/commands/server.py +45 -0
  20. collective.html2blocks-1.0.0a1/collective/html2blocks/converter.py +93 -0
  21. collective.html2blocks-1.0.0a1/collective/html2blocks/logger.py +20 -0
  22. collective.html2blocks-1.0.0a1/collective/html2blocks/registry.py +322 -0
  23. collective.html2blocks-1.0.0a1/collective/html2blocks/services/__init__.py +63 -0
  24. collective.html2blocks-1.0.0a1/collective/html2blocks/services/healthcheck.py +33 -0
  25. collective.html2blocks-1.0.0a1/collective/html2blocks/services/html.py +103 -0
  26. collective.html2blocks-1.0.0a1/collective/html2blocks/services/info.py +42 -0
  27. collective.html2blocks-1.0.0a1/collective/html2blocks/utils/__init__.py +1 -0
  28. collective.html2blocks-1.0.0a1/collective/html2blocks/utils/blocks.py +63 -0
  29. collective.html2blocks-1.0.0a1/collective/html2blocks/utils/generator.py +59 -0
  30. collective.html2blocks-1.0.0a1/collective/html2blocks/utils/inline.py +52 -0
  31. collective.html2blocks-1.0.0a1/collective/html2blocks/utils/markup.py +490 -0
  32. collective.html2blocks-1.0.0a1/collective/html2blocks/utils/slate.py +405 -0
  33. collective.html2blocks-1.0.0a1/pyproject.toml +169 -0
@@ -0,0 +1,18 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+ .mypy_cache
9
+ .pytest_cache
10
+ .ruff_cache
11
+ .coverage
12
+
13
+ # Test files
14
+ tmp.json
15
+ tmp.html
16
+
17
+ # Virtual environments
18
+ .venv
@@ -0,0 +1,47 @@
1
+ Metadata-Version: 2.4
2
+ Name: collective.html2blocks
3
+ Version: 1.0.0a1
4
+ Summary: Convert html to blocks to be used by Volto
5
+ Project-URL: Homepage, https://github.com/collective/collective.html2blocks#readme
6
+ Project-URL: Changelog, https://github.com/collective/collective.html2blocks/blob/main/CHANGELOG.md
7
+ Project-URL: Issues, https://github.com/collective/collective.html2blocks/issues
8
+ Project-URL: Source, https://github.com/collective/collective.html2blocks
9
+ Project-URL: Repository, https://github.com/collective/collective.html2blocks
10
+ Author-email: Érico Andrei <ericof@plone.org>
11
+ Keywords: Plone,Python,Volto
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Web Environment
14
+ Classifier: Framework :: Plone
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: GNU General Public License v2 (GPLv2)
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Requires-Python: >=3.12
22
+ Requires-Dist: beautifulsoup4>=4.13.3
23
+ Requires-Dist: fastapi[standard]>=0.116.1
24
+ Requires-Dist: python-slugify>=8.0.4
25
+ Requires-Dist: typer>=0.15.1
26
+ Description-Content-Type: text/markdown
27
+
28
+ <h1 align="center">collective.html2blocks</h1>
29
+
30
+ <div align="center">
31
+
32
+ [![PyPI](https://img.shields.io/pypi/v/collective.html2blocks)](https://pypi.org/project/collective.html2blocks/)
33
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/collective.html2blocks)](https://pypi.org/project/collective.html2blocks/)
34
+ [![PyPI - Wheel](https://img.shields.io/pypi/wheel/collective.html2blocks)](https://pypi.org/project/collective.html2blocks/)
35
+ [![PyPI - License](https://img.shields.io/pypi/l/collective.html2blocks)](https://pypi.org/project/collective.html2blocks/)
36
+ [![PyPI - Status](https://img.shields.io/pypi/status/collective.html2blocks)](https://pypi.org/project/collective.html2blocks/)
37
+
38
+
39
+ [![Code Quality](https://github.com/collective/collective.html2blocks/actions/workflows/qa.yml/badge.svg)](https://github.com/collective/collective.html2blocks/actions/workflows/qa.yml)
40
+ [![Tests](https://github.com/collective/collective.html2blocks/actions/workflows/test.yml/badge.svg)](https://github.com/collective/collective.html2blocks/actions/workflows/test.yml)
41
+
42
+ [![GitHub contributors](https://img.shields.io/github/contributors/collective/collective.html2blocks)](https://github.com/collective/collective.html2blocks)
43
+ [![GitHub Repo stars](https://img.shields.io/github/stars/collective/collective.html2blocks?style=social)](https://github.com/collective/collective.html2blocks)
44
+
45
+ </div>
46
+
47
+ **collective.html2blocks** helps with migrations from old Plone versions -- and other CMS -- to Volto.
@@ -0,0 +1,20 @@
1
+ <h1 align="center">collective.html2blocks</h1>
2
+
3
+ <div align="center">
4
+
5
+ [![PyPI](https://img.shields.io/pypi/v/collective.html2blocks)](https://pypi.org/project/collective.html2blocks/)
6
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/collective.html2blocks)](https://pypi.org/project/collective.html2blocks/)
7
+ [![PyPI - Wheel](https://img.shields.io/pypi/wheel/collective.html2blocks)](https://pypi.org/project/collective.html2blocks/)
8
+ [![PyPI - License](https://img.shields.io/pypi/l/collective.html2blocks)](https://pypi.org/project/collective.html2blocks/)
9
+ [![PyPI - Status](https://img.shields.io/pypi/status/collective.html2blocks)](https://pypi.org/project/collective.html2blocks/)
10
+
11
+
12
+ [![Code Quality](https://github.com/collective/collective.html2blocks/actions/workflows/qa.yml/badge.svg)](https://github.com/collective/collective.html2blocks/actions/workflows/qa.yml)
13
+ [![Tests](https://github.com/collective/collective.html2blocks/actions/workflows/test.yml/badge.svg)](https://github.com/collective/collective.html2blocks/actions/workflows/test.yml)
14
+
15
+ [![GitHub contributors](https://img.shields.io/github/contributors/collective/collective.html2blocks)](https://github.com/collective/collective.html2blocks)
16
+ [![GitHub Repo stars](https://img.shields.io/github/stars/collective/collective.html2blocks?style=social)](https://github.com/collective/collective.html2blocks)
17
+
18
+ </div>
19
+
20
+ **collective.html2blocks** helps with migrations from old Plone versions -- and other CMS -- to Volto.
@@ -0,0 +1,2 @@
1
+ __version__ = "1.0.0a1"
2
+ PACKAGE_NAME = "collective.html2blocks"
@@ -0,0 +1,119 @@
1
+ from bs4 import Tag
2
+ from collections.abc import Callable
3
+ from collections.abc import Generator
4
+ from collections.abc import Sequence
5
+ from dataclasses import dataclass
6
+ from typing import NotRequired
7
+ from typing import Protocol
8
+ from typing import TypedDict
9
+
10
+ import re
11
+
12
+
13
+ BaseVoltoBlock = TypedDict("BaseVoltoBlock", {"@type": str})
14
+
15
+
16
+ class VoltoBlock(BaseVoltoBlock, total=False):
17
+ """Volto Block type with optional fields."""
18
+
19
+ value: NotRequired[list]
20
+ align: NotRequired[str]
21
+ size: NotRequired[str]
22
+ url: NotRequired[str]
23
+ plaintext: NotRequired[str]
24
+
25
+
26
+ class SlateBlockItem(TypedDict, total=False):
27
+ """A child in an Slate Block."""
28
+
29
+ type: NotRequired[str]
30
+ text: NotRequired[str]
31
+ key: NotRequired[str]
32
+ cells: NotRequired[Sequence["SlateBlockItem"]]
33
+ children: NotRequired[list["SlateBlockItem"]]
34
+ value: NotRequired["SlateBlockItem | str"]
35
+
36
+
37
+ class BlocksLayout(TypedDict):
38
+ """Blocks layout information."""
39
+
40
+ items: Sequence[str]
41
+
42
+
43
+ class VoltoBlocksInfo(TypedDict):
44
+ """Volto Blocks information."""
45
+
46
+ blocks: dict[str, VoltoBlock]
47
+ blocks_layout: BlocksLayout
48
+
49
+
50
+ class BlockConverter(Protocol):
51
+ """Protocol for block converters."""
52
+
53
+ __name__: str
54
+ __orig_mod__: str
55
+
56
+ def __call__(self, element: Tag) -> Generator[VoltoBlock, None, None]: ...
57
+
58
+
59
+ class ElementConverterFunc(Protocol):
60
+ """Protocol for element converters."""
61
+
62
+ __name__: str
63
+ __module__: str
64
+
65
+ def __call__(
66
+ self, element: Tag, tag_name: str
67
+ ) -> Generator[VoltoBlock, None, SlateBlockItem]: ...
68
+
69
+
70
+ class ElementConverter(ElementConverterFunc):
71
+ """Protocol for element converters."""
72
+
73
+ __name__: str
74
+ __orig_mod__: str
75
+
76
+
77
+ SlateItemsGenerator = Generator[VoltoBlock | None, None, list[SlateBlockItem]]
78
+
79
+ SlateItemGenerator = Generator[VoltoBlock | None, None, SlateBlockItem | None]
80
+
81
+
82
+ @dataclass
83
+ class IFrameConverter:
84
+ url_pattern: str
85
+ provider: str
86
+ converter: Callable
87
+
88
+
89
+ @dataclass
90
+ class Registry:
91
+ block_converters: dict[str, BlockConverter]
92
+ element_converters: dict[str, ElementConverter]
93
+ iframe_converters: dict[re.Pattern | str, IFrameConverter]
94
+ default: BlockConverter | None = None
95
+
96
+
97
+ @dataclass
98
+ class EmbedInfo:
99
+ url: str
100
+ provider_id: str
101
+ converter: Callable
102
+
103
+
104
+ class ReportRegistrations(TypedDict):
105
+ """Report registrations type."""
106
+
107
+ block: dict[str, str]
108
+ element: dict[str, str]
109
+ iframe: dict[str, str]
110
+
111
+
112
+ __all__ = [
113
+ "EmbedInfo",
114
+ "Registry",
115
+ "ReportRegistrations",
116
+ "Tag",
117
+ "VoltoBlock",
118
+ "VoltoBlocksInfo",
119
+ ]
@@ -0,0 +1,56 @@
1
+ """
2
+ Block converters for collective.html2blocks.
3
+
4
+ This module imports and exposes the main block converter functions for HTML elements
5
+ such as iframe, image, slate (rich text), table, and video. These converters are
6
+ registered with the html2blocks registry and are used to transform HTML elements
7
+ into Volto blocks for use in Plone/Volto content management systems.
8
+
9
+ Each converter implements a specific strategy for handling its corresponding HTML tag:
10
+
11
+ - `iframe_block`: Converts <iframe> elements to Volto embed blocks,
12
+ handling provider-specific logic.
13
+ - `image_block`: Converts <img> elements to Volto image blocks,
14
+ extracting src, alt, and other attributes.
15
+ - `slate_block`: Converts rich text elements (paragraphs, headings,
16
+ lists, etc.) to Volto Slate blocks, supporting nested and inline content.
17
+ - `table_block`: Converts <table> elements to Volto table blocks,
18
+ handling headers, rows, and cell types.
19
+ - `video_block`: Converts <video> elements to Volto video blocks,
20
+ extracting sources and metadata.
21
+
22
+ These converters are registered using decorators and can be extended or referenced
23
+ by other developers to implement custom block conversion logic. See the implementation
24
+ of each block in its respective module for details on parsing, normalization, and
25
+ Volto block structure.
26
+
27
+ Example usage::
28
+
29
+ from collective.html2blocks.blocks import slate_block
30
+ blocks = slate_block(element)
31
+
32
+ For custom converters, use the registry decorators:
33
+
34
+ from collective.html2blocks.registry import block_converter
35
+
36
+ @block_converter('custom')
37
+ def custom_block(element):
38
+ ...
39
+
40
+ __all__ lists the available block converters for import and reference.
41
+ """
42
+
43
+ from .iframe import iframe_block
44
+ from .image import image_block
45
+ from .slate import slate_block
46
+ from .table import table_block
47
+ from .video import video_block
48
+
49
+
50
+ __all__ = [
51
+ "iframe_block",
52
+ "image_block",
53
+ "slate_block",
54
+ "table_block",
55
+ "video_block",
56
+ ]
@@ -0,0 +1,31 @@
1
+ from .soundcloud import soundcloud_block
2
+ from .youtube import youtube_block
3
+ from collections.abc import Generator
4
+ from collective.html2blocks import _types as t
5
+ from collective.html2blocks import registry
6
+ from collective.html2blocks.utils.markup import url_from_iframe
7
+
8
+
9
+ __all__ = [
10
+ "iframe_block",
11
+ "iframe_default_block",
12
+ "soundcloud_block",
13
+ "youtube_block",
14
+ ]
15
+
16
+
17
+ @registry.iframe_converter("iframe")
18
+ def iframe_default_block(
19
+ element: t.Tag, src: str, provider_id: str
20
+ ) -> Generator[t.VoltoBlock, None, None]:
21
+ """Implemented by @kitconcept/volto-iframe-block."""
22
+ height = element.get("height", "200px")
23
+ yield {"@type": "iframe", "src": src, "width": "full", "height": height}
24
+
25
+
26
+ @registry.block_converter("iframe")
27
+ def iframe_block(element: t.Tag) -> Generator[t.VoltoBlock, None, None]:
28
+ """Variations of the iframe block."""
29
+ src = url_from_iframe(element)
30
+ embed_info = registry.get_iframe_converter(src)
31
+ yield from embed_info.converter(element, embed_info.url, embed_info.provider_id)
@@ -0,0 +1,25 @@
1
+ from collective.html2blocks import _types as t
2
+ from collective.html2blocks import registry
3
+
4
+ import re
5
+
6
+
7
+ SOUNDCLOUD_REGEX = re.compile(
8
+ r"https://w\.soundcloud.com/player/\?url=https(%3A|:)//api.soundcloud.com/tracks/(?P<provider_id>\d+)(.*)$"
9
+ )
10
+
11
+
12
+ @registry.iframe_converter(
13
+ "soundcloud",
14
+ src_pattern=SOUNDCLOUD_REGEX,
15
+ url_pattern=r"https://api.soundcloud.com/tracks/\g<provider_id>\2",
16
+ )
17
+ def soundcloud_block(element: t.Tag, src: str, provider_id: str) -> list[t.VoltoBlock]:
18
+ """Implemented by @kitconcept/volto-social-blocks."""
19
+ block: t.VoltoBlock = {
20
+ "@type": "soundcloudBlock",
21
+ "soundcloudId": provider_id,
22
+ "align": "center",
23
+ "size": "l",
24
+ }
25
+ return [block]
@@ -0,0 +1,32 @@
1
+ from collective.html2blocks import _types as t
2
+ from collective.html2blocks import registry
3
+
4
+ import re
5
+
6
+
7
+ YOUTUBE_REGEX = re.compile(
8
+ r"^(http:|https:|)\/\/(m.|www.)?(youtu(be\.com|\.be|be\.googleapis\.com|be-nocookie\.com))\/(embed\/|watch\?v=|v\/)?(?P<provider_id>[A-Za-z0-9._%-]*)(\?\S+|\&\S+)?$"
9
+ )
10
+
11
+
12
+ def get_youtube_video_id(url: str) -> str:
13
+ video_id = ""
14
+ if match := re.match(YOUTUBE_REGEX, url):
15
+ video_id = match.group("provider_id")
16
+ return video_id
17
+
18
+
19
+ def _youtube_block(src: str) -> list[t.VoltoBlock]:
20
+ """Return a block to display youtube videos."""
21
+ block: t.VoltoBlock = {"@type": "video", "url": src}
22
+ return [block]
23
+
24
+
25
+ @registry.iframe_converter(
26
+ "youtube",
27
+ src_pattern=YOUTUBE_REGEX,
28
+ url_pattern=r"https://youtu.be/\g<provider_id>",
29
+ )
30
+ def youtube_block(element: t.Tag, src: str, provider_id: str) -> list[t.VoltoBlock]:
31
+ """Implemented by @plone/volto."""
32
+ return _youtube_block(src)
@@ -0,0 +1,112 @@
1
+ """
2
+ Image block converter for collective.html2blocks.
3
+
4
+ This module provides the block converter for <img> elements, transforming them
5
+ into Volto image blocks. It extracts image attributes, alignment, size, and
6
+ custom data attributes, supporting Plone/Volto conventions for image handling.
7
+
8
+ Implementation details:
9
+ - Alignment and size are inferred from CSS classes and image src patterns.
10
+ - Custom data attributes are added to the block for extensibility.
11
+ - The converter yields a Volto image block with all relevant metadata.
12
+
13
+ Example usage::
14
+
15
+ from collective.html2blocks.blocks.image import image_block
16
+ blocks = list(image_block(element))
17
+ """
18
+
19
+ from collections.abc import Generator
20
+ from collective.html2blocks import _types as t
21
+ from collective.html2blocks import registry
22
+ from collective.html2blocks.logger import logger
23
+
24
+ import re
25
+
26
+
27
+ def _align_from_classes(css_classes: list[str]) -> str:
28
+ align = "center"
29
+ if "image-left" in css_classes:
30
+ align = "left"
31
+ elif "image-right" in css_classes:
32
+ align = "right"
33
+ elif "image-inline" in css_classes:
34
+ align = "center"
35
+ return align
36
+
37
+
38
+ def _add_align_to_block(block: t.VoltoBlock, css_classes: list[str]) -> None:
39
+ size: str = "l"
40
+ match align := _align_from_classes(css_classes):
41
+ case "left":
42
+ size = "m"
43
+ case "right":
44
+ size = "m"
45
+ block["align"] = align
46
+ block["size"] = size
47
+
48
+
49
+ def _add_size_to_block(block: t.VoltoBlock, src: str) -> None:
50
+ size: str = block.get("size", "m")
51
+ match _scale_from_src(src):
52
+ case "large":
53
+ size = "l"
54
+ case "preview":
55
+ size = "l"
56
+ case "thumb":
57
+ size = "s"
58
+ case "tile":
59
+ size = "s"
60
+ block["size"] = size
61
+
62
+
63
+ def _add_data_attrs_to_block(block: t.VoltoBlock, attrs: dict) -> None:
64
+ data_attrs: dict = {k: v for k, v in attrs.items() if k.startswith("data-")}
65
+ for raw_key, value in data_attrs.items():
66
+ key = raw_key.replace("data-", "")
67
+ block[key] = value
68
+
69
+
70
+ def _scale_from_src(src: str) -> str:
71
+ scale = ""
72
+ if (match := re.search("/@@images/(?P<field>[^/]*)/(?P<scale>.+)", src)) or (
73
+ match := re.search("/image_(?P<scale>.+)", src)
74
+ ):
75
+ scale = match.group("scale")
76
+ elif match := re.search("/image$", src):
77
+ scale = "original"
78
+ return scale
79
+
80
+
81
+ @registry.block_converter("img")
82
+ def image_block(element: t.Tag) -> Generator[t.VoltoBlock, None, None]:
83
+ """
84
+ Convert an <img> element to a Volto image block.
85
+
86
+ This converter extracts src, alt, title, alignment, size, and custom data
87
+ attributes from the image element and yields a Volto image block.
88
+
89
+ Args:
90
+ element (Tag): The <img> element to convert.
91
+
92
+ Yields:
93
+ VoltoBlock: The converted image block.
94
+
95
+ Example::
96
+
97
+ blocks = list(image_block(element))
98
+ # [{'@type': 'image', 'url': ..., 'alt': ..., ...}]
99
+ """
100
+ src: str = element.get("src")
101
+ if src is None:
102
+ logger.debug(f"Dropping element {element}")
103
+ return None
104
+ url: str = src.split("/@@images")[0]
105
+ css_classes: list[str] = element.get("class", [])
106
+ alt: str = element.get("alt", "")
107
+ title: str = element.get("title", "")
108
+ block: t.VoltoBlock = {"@type": "image", "url": url, "alt": alt, "title": title}
109
+ _add_align_to_block(block, css_classes)
110
+ _add_size_to_block(block, src)
111
+ _add_data_attrs_to_block(block, element.attrs)
112
+ yield block
@@ -0,0 +1,69 @@
1
+ """
2
+ Slate block converter for collective.html2blocks.
3
+
4
+ This module provides the default block converter for rich text elements,
5
+ transforming paragraphs, headings, lists, and other inline/nested content into
6
+ Volto Slate blocks. It uses the parser to deserialize HTML elements and
7
+ normalizes the result for Volto consumption.
8
+
9
+ Implementation details:
10
+ - Handles plain text extraction and normalization.
11
+ - Supports nested and inline content, grouping and flattening as needed.
12
+ - Yields Volto Slate blocks and any additional blocks found during parsing.
13
+
14
+ Example usage::
15
+
16
+ from collective.html2blocks.blocks.slate import slate_block
17
+ blocks = list(slate_block(element))
18
+ """
19
+
20
+ from . import parser
21
+ from collections.abc import Generator
22
+ from collective.html2blocks import _types as t
23
+ from collective.html2blocks import registry
24
+ from collective.html2blocks.utils import markup
25
+ from collective.html2blocks.utils import slate as utils
26
+
27
+
28
+ @registry.default_converter
29
+ def slate_block(
30
+ element: t.Tag,
31
+ ) -> Generator[t.VoltoBlock, None, t.VoltoBlock | None]:
32
+ """
33
+ Convert rich text HTML elements to Volto Slate blocks.
34
+
35
+ This converter deserializes paragraphs, headings, lists, and other content
36
+ into Volto Slate blocks, handling nested and inline elements, and normalizing
37
+ the result for Volto. Additional blocks found during parsing are also yielded.
38
+
39
+ Args:
40
+ element (Tag): The HTML element to convert.
41
+
42
+ Yields:
43
+ VoltoBlock: The converted Slate block and any additional blocks.
44
+
45
+ Example::
46
+
47
+ blocks = list(slate_block(element))
48
+ # [{'@type': 'slate', 'plaintext': ..., 'value': [...]}]
49
+ """
50
+ plaintext = markup.extract_plaintext(element)
51
+ value = yield from parser.deserialize(element)
52
+ blocks: list[t.VoltoBlock] = []
53
+ additional_blocks: list[t.VoltoBlock] = []
54
+ if value is None:
55
+ value = []
56
+ elif isinstance(value, list):
57
+ value = yield from parser.extract_blocks(value)
58
+ elif isinstance(value, dict) and (children := value.get("children", [])):
59
+ children = yield from parser.extract_blocks(children)
60
+ value["children"] = children
61
+ if value and not blocks:
62
+ value = [value] if not isinstance(value, list) else value
63
+ value = utils.process_top_level_items(value)
64
+ block = {"@type": "slate", "plaintext": plaintext, "value": value}
65
+ block = yield from parser.finalize_slate(block)
66
+ blocks = [block]
67
+ yield from blocks
68
+ yield from additional_blocks
69
+ return None