Coverage for pyeditorjs/parser.py: 82%

1 import typing as t

2 import warnings

3 from dataclasses import dataclass

4

5 from .blocks import BLOCKS_MAP, EditorJsBlock

6 from .exceptions import EditorJsParseError, EditorJSUnsupportedBlock

7

8

9@dataclass

10 class EditorJsParser:

11 """

12 An Editor.js parser.

13 """

14

15 content: dict

16 """The JSON data of Editor.js content."""

17

18 def __post_init__(self) -> None:

19 if not isinstance(self.content, dict):

20 raise EditorJsParseError(

21 f"Content must be `dict`, not {type(self.content).__name__}"

22 )

23

24 @staticmethod

25 def _get_block(data: dict, strict: bool = False) -> t.Optional[EditorJsBlock]:

26 """

27 Obtains block instance from block data.

28 """

29

30 _type = data.get("type", None)

31

32 if _type not in BLOCKS_MAP:

33 if strict:

34 raise EditorJSUnsupportedBlock(_type)

35 else:

36 warnings.warn(f"Unsupported block: {_type}", category=RuntimeWarning)

37 return None

38

39 return BLOCKS_MAP[_type](_data=data)

40

41 def blocks(self, strict: bool = False) -> list[EditorJsBlock]:

42 """

43 Obtains a list of all available blocks from the editor's JSON data.

44 """

45

46 all_blocks: list[EditorJsBlock] = []

47 blocks = self.content.get("blocks", [])

48

49 if not isinstance(blocks, list):

50 raise EditorJsParseError(

51 f"Blocks is not `list`, but `{type(blocks).__name__}`"

52 )

53

54 for block_data in blocks:

55 if block := self._get_block(data=block_data, strict=strict):

56 all_blocks.append(block)

57

58 return all_blocks

59

60 def __iter__(self) -> t.Iterator[EditorJsBlock]:

61 """Returns `iter(self.blocks())`"""

62

63 return iter(self.blocks())

64

65 def html(self, sanitize: bool = False, strict: bool = False) -> str:

66 """

67 Renders the editor's JSON content as HTML.

68

69 ### Parameters:

70 - `sanitize` - whether to also sanitize the blocks' texts/contents.

71 """

72

73 return "\n".join(

74 [block.html(sanitize=sanitize) for block in self.blocks(strict=strict)]

75 )