draftjs_exporter 6.0.0__tar.gz → 7.2.0__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 (52) hide show
  1. {draftjs_exporter-6.0.0 → draftjs_exporter-7.2.0}/PKG-INFO +3 -4
  2. draftjs_exporter-7.2.0/draftjs_exporter/.agents/skills/draftjs-exporter/SKILL.md +340 -0
  3. draftjs_exporter-7.2.0/draftjs_exporter/__init__.py +194 -0
  4. {draftjs_exporter-6.0.0 → draftjs_exporter-7.2.0}/draftjs_exporter/command.py +2 -2
  5. {draftjs_exporter-6.0.0 → draftjs_exporter-7.2.0}/draftjs_exporter/composite_decorators.py +2 -2
  6. draftjs_exporter-7.2.0/draftjs_exporter/contentstate_filter/__init__.py +267 -0
  7. {draftjs_exporter-6.0.0 → draftjs_exporter-7.2.0}/draftjs_exporter/dom.py +31 -6
  8. {draftjs_exporter-6.0.0 → draftjs_exporter-7.2.0}/draftjs_exporter/engines/html5lib.py +4 -7
  9. {draftjs_exporter-6.0.0 → draftjs_exporter-7.2.0}/draftjs_exporter/engines/lxml.py +4 -2
  10. {draftjs_exporter-6.0.0 → draftjs_exporter-7.2.0}/draftjs_exporter/engines/markdown.py +81 -9
  11. {draftjs_exporter-6.0.0 → draftjs_exporter-7.2.0}/draftjs_exporter/engines/string.py +2 -2
  12. {draftjs_exporter-6.0.0 → draftjs_exporter-7.2.0}/draftjs_exporter/entity_state.py +13 -8
  13. draftjs_exporter-7.2.0/draftjs_exporter/error.py +30 -0
  14. {draftjs_exporter-6.0.0 → draftjs_exporter-7.2.0}/draftjs_exporter/html.py +24 -9
  15. {draftjs_exporter-6.0.0 → draftjs_exporter-7.2.0}/draftjs_exporter/markdown/__init__.py +67 -66
  16. draftjs_exporter-7.2.0/draftjs_exporter/markdown/blocks.py +108 -0
  17. draftjs_exporter-7.2.0/draftjs_exporter/markdown/code.py +41 -0
  18. draftjs_exporter-7.2.0/draftjs_exporter/markdown/entities.py +78 -0
  19. draftjs_exporter-7.2.0/draftjs_exporter/markdown/escape.py +211 -0
  20. {draftjs_exporter-6.0.0 → draftjs_exporter-7.2.0}/draftjs_exporter/markdown/fallbacks.py +5 -5
  21. draftjs_exporter-7.2.0/draftjs_exporter/markdown/helpers.py +157 -0
  22. draftjs_exporter-7.2.0/draftjs_exporter/markdown/lists.py +234 -0
  23. draftjs_exporter-7.2.0/draftjs_exporter/markdown/styles.py +52 -0
  24. draftjs_exporter-7.2.0/draftjs_exporter/markdown_importer/__init__.py +63 -0
  25. draftjs_exporter-7.2.0/draftjs_exporter/markdown_parser/__init__.py +129 -0
  26. draftjs_exporter-7.2.0/draftjs_exporter/markdown_parser/blocks.py +346 -0
  27. draftjs_exporter-7.2.0/draftjs_exporter/markdown_parser/builder.py +88 -0
  28. draftjs_exporter-7.2.0/draftjs_exporter/markdown_parser/inline.py +526 -0
  29. draftjs_exporter-7.2.0/draftjs_exporter/markdown_parser/resolvers.py +129 -0
  30. {draftjs_exporter-6.0.0 → draftjs_exporter-7.2.0}/draftjs_exporter/types.py +3 -5
  31. {draftjs_exporter-6.0.0 → draftjs_exporter-7.2.0}/draftjs_exporter/wrapper_state.py +63 -27
  32. draftjs_exporter-7.2.0/pyproject.toml +236 -0
  33. draftjs_exporter-6.0.0/pyproject.toml → draftjs_exporter-7.2.0/pyproject.toml.orig +151 -14
  34. draftjs_exporter-6.0.0/draftjs_exporter/__init__.py +0 -98
  35. draftjs_exporter-6.0.0/draftjs_exporter/error.py +0 -9
  36. draftjs_exporter-6.0.0/draftjs_exporter/markdown/blocks.py +0 -62
  37. draftjs_exporter-6.0.0/draftjs_exporter/markdown/code.py +0 -42
  38. draftjs_exporter-6.0.0/draftjs_exporter/markdown/entities.py +0 -43
  39. draftjs_exporter-6.0.0/draftjs_exporter/markdown/helpers.py +0 -28
  40. draftjs_exporter-6.0.0/draftjs_exporter/markdown/lists.py +0 -106
  41. draftjs_exporter-6.0.0/draftjs_exporter/markdown/styles.py +0 -16
  42. {draftjs_exporter-6.0.0 → draftjs_exporter-7.2.0}/README.md +0 -0
  43. {draftjs_exporter-6.0.0 → draftjs_exporter-7.2.0}/draftjs_exporter/constants.py +0 -0
  44. {draftjs_exporter-6.0.0 → draftjs_exporter-7.2.0}/draftjs_exporter/defaults.py +0 -0
  45. {draftjs_exporter-6.0.0 → draftjs_exporter-7.2.0}/draftjs_exporter/engines/__init__.py +0 -0
  46. {draftjs_exporter-6.0.0 → draftjs_exporter-7.2.0}/draftjs_exporter/engines/base.py +0 -0
  47. {draftjs_exporter-6.0.0 → draftjs_exporter-7.2.0}/draftjs_exporter/engines/string_compat.py +0 -0
  48. {draftjs_exporter-6.0.0 → draftjs_exporter-7.2.0}/draftjs_exporter/options.py +0 -0
  49. {draftjs_exporter-6.0.0 → draftjs_exporter-7.2.0}/draftjs_exporter/py.typed +0 -0
  50. {draftjs_exporter-6.0.0 → draftjs_exporter-7.2.0}/draftjs_exporter/style_state.py +0 -0
  51. {draftjs_exporter-6.0.0 → draftjs_exporter-7.2.0}/draftjs_exporter/utils/__init__.py +0 -0
  52. {draftjs_exporter-6.0.0 → draftjs_exporter-7.2.0}/draftjs_exporter/utils/module_loading.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: draftjs_exporter
3
- Version: 6.0.0
3
+ Version: 7.2.0
4
4
  Summary: Library to convert rich text from Draft.js raw ContentState to HTML
5
5
  License-Expression: MIT
6
6
  Classifier: Environment :: Web Environment
@@ -9,7 +9,6 @@ Classifier: Development Status :: 5 - Production/Stable
9
9
  Classifier: Operating System :: OS Independent
10
10
  Classifier: Programming Language :: Python
11
11
  Classifier: Programming Language :: Python :: 3
12
- Classifier: Programming Language :: Python :: 3.10
13
12
  Classifier: Programming Language :: Python :: 3.11
14
13
  Classifier: Programming Language :: Python :: 3.12
15
14
  Classifier: Programming Language :: Python :: 3.13
@@ -24,8 +23,8 @@ Classifier: Topic :: Text Editors :: Word Processors
24
23
  Classifier: Typing :: Typed
25
24
  Requires-Dist: beautifulsoup4>=4.4.1,<5 ; extra == 'html5lib'
26
25
  Requires-Dist: html5lib>=0.999,<2 ; extra == 'html5lib'
27
- Requires-Dist: lxml>=4.6.5,<7 ; extra == 'lxml'
28
- Requires-Python: >=3.10
26
+ Requires-Dist: lxml>=4.9.4,<7 ; extra == 'lxml'
27
+ Requires-Python: >=3.11
29
28
  Project-URL: Documentation, https://wagtail.github.io/draftjs_exporter/
30
29
  Project-URL: Source, https://github.com/wagtail/draftjs_exporter
31
30
  Project-URL: Tracker, https://github.com/wagtail/draftjs_exporter/issues
@@ -0,0 +1,340 @@
1
+ ---
2
+ name: draftjs-exporter
3
+ description: Use when working with the Draft.js exporter library. Manipulating and rendering Draft.js ContentState to HTML or Markdown, parsing Markdown back into ContentState, writing custom block/entity/style components, configuring block/style/entity maps, picking or building DOM, or extending the exporter with fallbacks and composite decorators. Trigger on imports from `draftjs_exporter`, `DOM.create_element`, `block_map` / `style_map` / `entity_decorators` / `composite_decorators`, `build_markdown_config`, `MarkdownImporter` / `ContentStateFilter` / `scheme_resolver`, or Draft.js `ContentState` / `entityMap` JSON.
4
+ license: MIT
5
+ metadata:
6
+ version: "6.0.0"
7
+ ---
8
+
9
+ # Draft.js exporter
10
+
11
+ Python library converting Draft.js raw [ContentState](https://wagtail.github.io/draftjs_exporter/content-state/) JSON into HTML or Markdown. Maintained by [Wagtail](https://wagtail.org/) contributors, developed alongside the [Draftail](https://www.draftail.org/) rich text editor.
12
+
13
+ The public API is small: an `HTMLExporter` class, a `DOM` namespace with a React-like `create_element`, default config maps (`BLOCK_MAP`, `STYLE_MAP`) and helpers (`code_block`, `render_children`), Markdown helpers (`md_*`), constants (`BLOCK_TYPES`, `ENTITY_TYPES`, `INLINE_STYLES`), and type aliases (`Props`, `Element`, `Component`, `ContentState`). Almost everything else is configuration.
14
+
15
+ ## Quick reference
16
+
17
+ You can access a Markdown-native version of every documentation page by adding `index.md` at the end of the URL.
18
+
19
+ | Task | Solution | Docs |
20
+ | --------------------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
21
+ | Render ContentState to HTML | `HTML({}).render(content_state)` | [getting-started](https://wagtail.github.io/draftjs_exporter/getting-started/) |
22
+ | Override default blocks / styles | `**BLOCK_MAP`, `**STYLE_MAP` then override keys | [block map](https://wagtail.github.io/draftjs_exporter/configuration/#block-map) |
23
+ | Add HTML attributes to a block | `BLOCK_TYPES.X: {"element": "h3", "props": {"class": "…"}}` | [block map](https://wagtail.github.io/draftjs_exporter/configuration/#block-map) |
24
+ | Wrap adjacent blocks (lists) | `"wrapper": "ul", "wrapper_props": {"class": "…"}` | [block map](https://wagtail.github.io/draftjs_exporter/configuration/#block-map) |
25
+ | Render entity data (image, link) | `"entity_decorators": {ENTITY_TYPES.LINK: link}` | [entity components](https://wagtail.github.io/draftjs_exporter/custom-components/#entity-components) |
26
+ | Read block data / depth in a component | `props["block"]["data"]`, `props["block"]["depth"]` | [block components](https://wagtail.github.io/draftjs_exporter/custom-components/#block-components) |
27
+ | Compose components / pass children | `DOM.create_element(type, props, *children)` | [nesting](https://wagtail.github.io/draftjs_exporter/custom-components/#nesting-and-reusing-components) |
28
+ | Replace text by regex (line breaks, mentions) | `"composite_decorators": [{"strategy": rx, "component": fn}]` | [composite decorators](https://wagtail.github.io/draftjs_exporter/configuration/#composite-decorators) |
29
+ | Handle missing block / style / entity types | `BLOCK_TYPES.FALLBACK`, `INLINE_STYLES.FALLBACK`, `ENTITY_TYPES.FALLBACK` | [fallbacks](https://wagtail.github.io/draftjs_exporter/fallback-components/) |
30
+ | Discard an entity type | `ENTITY_TYPES.EMBED: None` | [entity decorators](https://wagtail.github.io/draftjs_exporter/configuration/#entity-decorators) |
31
+ | Switch DOM engine | `"engine": DOM.HTML5LIB` (or `DOM.LXML`, `DOM.STRING_COMPAT`) | [alternative engines](https://wagtail.github.io/draftjs_exporter/alternative-engines/) |
32
+ | Render Markdown instead of HTML | `HTML(MARKDOWN_CONFIG).render(content_state)` | [Markdown](https://wagtail.github.io/draftjs_exporter/markdown/) |
33
+ | Customize Markdown characters | `build_markdown_config({"bold": "__", "italic": "*", ...})` | [Markdown chars](https://wagtail.github.io/draftjs_exporter/markdown/#configuring-output-characters) |
34
+ | Import Markdown as ContentState | `MarkdownImporter({}).import_markdown(md)` | [Markdown importer](https://wagtail.github.io/draftjs_exporter/markdown-importer/) |
35
+ | Resolve internal URLs on import (wagtail://) | `scheme_resolver("wagtail", {"page": "LINK"}, coerce={"id": int})` | [Entity resolution](https://wagtail.github.io/draftjs_exporter/markdown-importer/#entity-resolution) |
36
+ | Filter imported content (demote headings) | `ContentStateFilter([{"type": "block", "match": ..., "action": "demote"}])` | [Filtering](https://wagtail.github.io/draftjs_exporter/markdown-importer/#filtering) |
37
+ | Debug output | `DOM.render_debug(elt)`, `echo '{...}' \| python example.py -` | [API](https://wagtail.github.io/draftjs_exporter/api/) |
38
+ | Migrate between major versions | `DOM.STRING_COMPAT` for old `string` output; per-version notes | [migration guide](https://wagtail.github.io/draftjs_exporter/migration-guide/) |
39
+ | Full public API (constants, types, defaults) | `draftjs_exporter.constants`, `types`, `defaults` | [API reference](https://wagtail.github.io/draftjs_exporter/api/) |
40
+
41
+ ## Quick start
42
+
43
+ Install `draftjs_exporter` from PyPI.
44
+
45
+ ```python
46
+ from draftjs_exporter import HTML
47
+
48
+ exporter = HTML({}) # empty config = use default block/style/entity maps
49
+
50
+ html = exporter.render(
51
+ {
52
+ "entityMap": {},
53
+ "blocks": [
54
+ {
55
+ "key": "6m5fh",
56
+ "text": "Hello, world!",
57
+ "type": "unstyled",
58
+ "depth": 0,
59
+ "inlineStyleRanges": [],
60
+ "entityRanges": [],
61
+ }
62
+ ],
63
+ }
64
+ )
65
+ ```
66
+
67
+ Debug with real JSON: `echo '{"json": "contents"}' | python example.py -`. See [getting-started](https://wagtail.github.io/draftjs_exporter/getting-started/).
68
+
69
+ ## Configuration
70
+
71
+ The config is a single dict passed to `HTML()` with four optional keys plus `engine`. Each map extends the built-in defaults (`BLOCK_MAP`, `STYLE_MAP`) — spread them with `**` and override individual keys.
72
+
73
+ ```python
74
+ from draftjs_exporter import BLOCK_MAP, BLOCK_TYPES, DOM, ENTITY_TYPES, HTML, STYLE_MAP
75
+ import re
76
+
77
+ config = {
78
+ "block_map": {
79
+ **BLOCK_MAP,
80
+ BLOCK_TYPES.HEADER_TWO: "h2", # string: tag name
81
+ BLOCK_TYPES.HEADER_THREE: {
82
+ "element": "h3",
83
+ "props": {"class": "u-text-center"},
84
+ },
85
+ BLOCK_TYPES.UNORDERED_LIST_ITEM: { # wrapper for adjacent blocks
86
+ "element": "li",
87
+ "wrapper": "ul",
88
+ "wrapper_props": {"class": "bullet-list"},
89
+ },
90
+ },
91
+ "style_map": {
92
+ **STYLE_MAP,
93
+ "KBD": "kbd",
94
+ "HIGHLIGHT": {
95
+ "element": "strong",
96
+ "props": {"style": {"textDecoration": "underline"}},
97
+ },
98
+ },
99
+ "entity_decorators": {
100
+ ENTITY_TYPES.LINK: lambda props: DOM.create_element(
101
+ "a", {"href": props["url"]}, props["children"]
102
+ ),
103
+ ENTITY_TYPES.EMBED: None, # None discards this entity type
104
+ },
105
+ "composite_decorators": [
106
+ {
107
+ "strategy": re.compile(r"\n"),
108
+ "component": br,
109
+ }, # text transformations by regex
110
+ ],
111
+ "engine": DOM.STRING, # default; see Engines below
112
+ }
113
+
114
+ exporter = HTML(config)
115
+ ```
116
+
117
+ See [configuration reference](https://wagtail.github.io/draftjs_exporter/configuration/) for the full shape.
118
+
119
+ ### Conventions
120
+
121
+ - **Extend `BLOCK_MAP` and `STYLE_MAP` with `**` spread instead of rebuilding from scratch — they cover the common Draft.js types and styles.
122
+ - **Use `BLOCK_TYPES` / `INLINE_STYLES` / `ENTITY_TYPES` constants** instead of raw strings, so renames surface as test failures. They also expose `FALLBACK`.
123
+ - **Pick a component function only when you need block data, depth, or children composition.** A plain string or dict covers most cases.
124
+ - **Stick with the default `string` engine** unless you need HTML sanitization (`html5lib`/`lxml`).
125
+
126
+ ## Custom components
127
+
128
+ The component API mirrors React's `createElement`: a function takes a `props` dict and returns an `Element`. The `props` shape differs between entities, blocks, and styles. Reference components from `entity_decorators` (entities) or `block_map` / `style_map` (blocks and styles).
129
+
130
+ ```python
131
+ from draftjs_exporter import DOM, Element, Props
132
+
133
+
134
+ # Entity component: receives the entity's `data` dict as props.
135
+ def image(props: Props) -> Element:
136
+ """Render an image element from entity data."""
137
+ return DOM.create_element(
138
+ "img",
139
+ {
140
+ "src": props.get("src"),
141
+ "width": props.get("width"),
142
+ "height": props.get("height"),
143
+ "alt": props.get("alt"),
144
+ },
145
+ )
146
+
147
+
148
+ # Block component: receives `block` (Draft.js block object) and `children`.
149
+ def blockquote(props: Props) -> Element:
150
+ """Render a blockquote with an optional cite attribute."""
151
+ block_data = props["block"]["data"]
152
+ return DOM.create_element(
153
+ "blockquote", {"cite": block_data.get("cite")}, props["children"]
154
+ )
155
+ ```
156
+
157
+ Compose by passing extra positional children to `DOM.create_element(type, props, *children)`. Children can be strings, DOM elements, other components, or `None` (renders nothing). Pass `props["children"]` as the last argument so the block's content renders inside the wrapping element. See [custom components](https://wagtail.github.io/draftjs_exporter/custom-components/).
158
+
159
+ ### Component props and return values
160
+
161
+ | Component for | `props` contains |
162
+ | ---------------------------- | ---------------------------------------------------------------------------------------------------------------- |
163
+ | Entity (`entity_decorators`) | the entity's `data` keys, plus `children` and `entity` (`type`, `mutability`, `block`, `blocks`, `entity_range`) |
164
+ | Block (`block_map`) | `block`, `blocks`, `children` |
165
+ | Inline style (`style_map`) | `block`, `blocks`, `children`, `inline_style_range` |
166
+
167
+ The style name is `props["inline_style_range"]["style"]`; there is no `props["style"]`. Fallback components receive the same props as a regular component of the same kind.
168
+
169
+ ```python
170
+ def style_fallback(props: Props) -> Element:
171
+ name = props["inline_style_range"]["style"]
172
+ return DOM.create_element("span", {"class": name.lower()}, props["children"])
173
+
174
+
175
+ def trusted_markup(props: Props) -> Element:
176
+ return DOM.parse_html(props["markup"]) # one argument, output unescaped
177
+ ```
178
+
179
+ `DOM.create_element` with `None` as the type returns an empty fragment and ignores its children. To group children without a wrapping tag, use `DOM.create_element("fragment", {}, child_a, child_b)`. `ENTITY_TYPES` only has `LINK`, `DOCUMENT`, `IMAGE`, `EMBED`, `HORIZONTAL_RULE` and `FALLBACK`; key any other entity type by its raw string.
180
+
181
+ ### Fallbacks
182
+
183
+ Each map accepts a `FALLBACK` key (`BLOCK_TYPES.FALLBACK`, `INLINE_STYLES.FALLBACK`, `ENTITY_TYPES.FALLBACK`) triggered when the exporter hits a type with no explicit mapping. A fallback can return `props["children"]` (keep content, drop wrapper), `None` (remove entirely), or any DOM element (alternative rendering). Useful during development and migrations. See [fallback components](https://wagtail.github.io/draftjs_exporter/fallback-components/).
184
+
185
+ ## Engines
186
+
187
+ Engines are pluggable serialization strategies selected at runtime via the `engine` config key. Use the `DOM` class constants:
188
+
189
+ | Constant | Extra install | Notes |
190
+ | ------------------- | ------------------------------------------------------ | ------------------------------------------------- |
191
+ | `DOM.STRING` | none (default) | Fast, dependency-free, `parse_html` unsanitized |
192
+ | `DOM.HTML5LIB` | `pip install draftjs_exporter[html5lib]` | `parse_html` parses and normalizes markup |
193
+ | `DOM.LXML` | `pip install draftjs_exporter[lxml]` + libxml2/libxslt | Same as `html5lib`, alphabetical attrs |
194
+ | `DOM.STRING_COMPAT` | none | Byte-identical to first release of `string` |
195
+ | `DOM.MARKDOWN` | none | Produces Markdown — use `MARKDOWN_CONFIG` instead |
196
+
197
+ **Engines are not guaranteed to produce byte-identical output.** Real differences: attribute ordering (alphabetical for `lxml`/`html5lib`, insertion order for `string`), quote escaping in attributes, attribute-name validation. Expect minor output differences when switching engines — re-check tests that compare rendered HTML exactly. See [troubleshooting: exporter behavior](https://wagtail.github.io/draftjs_exporter/troubleshooting/#exporter-behavior).
198
+
199
+ To build a custom engine, subclass `DOMEngine` and implement `create_tag`, `append_child`, `render`. Reference it by dotted path: `"engine": "my_project.example.DOMListTree"`. Built-in engines also accept short names: `"engine": "string"` etc. See [custom engines](https://wagtail.github.io/draftjs_exporter/custom-engines/).
200
+
201
+ ## Markdown
202
+
203
+ Markdown output is **experimental**. Prefer `MARKDOWN_CONFIG` and `build_markdown_config` over hand-rolling a Markdown config.
204
+
205
+ ```python
206
+ from draftjs_exporter import HTML, MARKDOWN_CONFIG
207
+
208
+ exporter = HTML(MARKDOWN_CONFIG)
209
+ markdown = exporter.render(content_state)
210
+ ```
211
+
212
+ Customize characters and fallbacks with `build_markdown_config`:
213
+
214
+ ````python
215
+ from draftjs_exporter import HTML, build_markdown_config
216
+
217
+ config = build_markdown_config(
218
+ {
219
+ "bold": "__",
220
+ "italic": "*",
221
+ "unordered_list_marker": "*",
222
+ "ordered_list_delimiter": ")",
223
+ "horizontal_rule": "---",
224
+ "code_fence": "```",
225
+ "style_fallback": None, # None disables fallback (raises instead)
226
+ }
227
+ )
228
+ exporter = HTML(config)
229
+ ````
230
+
231
+ `MARKDOWN_CONFIG` is a regular config dict (`block_map`, `style_map`, `entity_decorators`, `engine`): extend its maps with `**` to change one type and keep the rest. In Markdown components, plain strings are escaped as user text (a returned `"**"` is output as `\*\*`), and a tag name such as `"span"` is output as literal `<span>` HTML. Emit Markdown syntax with these helpers:
232
+
233
+ - `md_inline_style(mark)` is a factory: it returns a style component wrapping children in `mark` on both sides.
234
+ - `md_mark_safe(markup)` returns an element rendered verbatim, unescaped.
235
+ - `md_inline(children)` takes a single list and returns a fragment without a wrapper.
236
+
237
+ ```python
238
+ from draftjs_exporter import (
239
+ HTML,
240
+ MARKDOWN_CONFIG,
241
+ md_inline,
242
+ md_inline_style,
243
+ md_mark_safe,
244
+ )
245
+
246
+
247
+ def footnote(props):
248
+ # Replaces the covered text: props["children"] is not used.
249
+ return md_inline([md_mark_safe("[^"), props["ref"], md_mark_safe("]")])
250
+
251
+
252
+ config = {
253
+ **MARKDOWN_CONFIG,
254
+ "style_map": {**MARKDOWN_CONFIG["style_map"], "SPOILER": md_inline_style("||")},
255
+ "entity_decorators": {**MARKDOWN_CONFIG["entity_decorators"], "FOOTNOTE": footnote},
256
+ }
257
+ ```
258
+
259
+ All defaults produce valid [CommonMark](https://commonmark.org/). The exporter [escapes](https://wagtail.github.io/draftjs_exporter/markdown/#escaping) user text so it renders literally rather than as Markdown syntax. Limitations: no underline/subscript/reference-style links/tables; partial bold/italic overlap can produce markers strict parsers reject. See [Markdown support](https://wagtail.github.io/draftjs_exporter/markdown/).
260
+
261
+ ### Importer
262
+
263
+ The Markdown importer (`MarkdownImporter`) parses Markdown back into Draft.js `ContentState`, enabling round-trip workflows (`ContentState → Markdown → ContentState`). It is dependency-free and covers the CommonMark core. Parsing runs first, then optional filtering applies content policy.
264
+
265
+ ```python
266
+ from draftjs_exporter import BLOCK_TYPES, MarkdownImporter, scheme_resolver
267
+
268
+ importer = MarkdownImporter(
269
+ {
270
+ "parser_config": {
271
+ # Disable constructs, or resolve internal URL schemes to typed entities.
272
+ "image_resolvers": [
273
+ scheme_resolver(
274
+ "wagtail", {"image": "IMAGE"}, coerce={"id": int}, label_key="alt"
275
+ ),
276
+ ],
277
+ # Whitelist inline HTML tags as styles (no Markdown equivalent).
278
+ "inline_html_styles": {"sup": "SUPERSCRIPT", "sub": "SUBSCRIPT"},
279
+ },
280
+ "filter_rules": [
281
+ # Declarative content policy: remove, keep, demote, or a callable.
282
+ {"type": "block", "match": BLOCK_TYPES.HEADER_ONE, "action": "demote"},
283
+ {"type": "inline_style", "match": "UNDERLINE", "action": "remove"},
284
+ {
285
+ "type": "block",
286
+ "match": BLOCK_TYPES.CODE,
287
+ "action": lambda block: {**block, "type": BLOCK_TYPES.UNSTYLED},
288
+ },
289
+ ],
290
+ }
291
+ )
292
+ content_state = importer.import_markdown(markdown)
293
+ ```
294
+
295
+ Filter rules:
296
+
297
+ - `type` is `"block"`, `"inline_style"`, or `"entity"`. `match` is the block type, style name, or entity type.
298
+ - `"remove"` deletes the match: the whole block, the style range (text stays), or the entity (text stays).
299
+ - `"demote"` only applies to `header-one` through `header-five` blocks, and raises for anything else. Change other block types with a callable.
300
+ - A callable receives the matched object and returns its replacement, or `None` to remove it: a block or entity dict for `"block"` / `"entity"` rules, the style name for `"inline_style"` rules.
301
+ - `ContentStateFilter(rules).apply(content_state)` applies the same rules to any ContentState, without Markdown.
302
+
303
+ See [Markdown importer](https://wagtail.github.io/draftjs_exporter/markdown-importer/). The importer inverts the exporter's text escaping and sized code-span delimiters on round-trip; see [Known round-trip limitations](https://wagtail.github.io/draftjs_exporter/markdown-importer/#known-round-trip-limitations) for the remaining gaps.
304
+
305
+ ## Common gotchas
306
+
307
+ 1. **`entity` and `children` are reserved `props` keys.** The exporter overrides them — `entity` becomes a dict with `type`/`mutability`, and `children` becomes the already-rendered content. Pick entity `data` keys that avoid them; there is no workaround. See [entity props override](https://wagtail.github.io/draftjs_exporter/troubleshooting/#entity-props-override).
308
+ 2. **Every engine escapes text.** A string returned by a component, or passed as a child to `DOM.create_element`, renders as text with `<`, `>` and `&` escaped, including with the default `string` engine. To output trusted markup unescaped, return `DOM.parse_html(markup)`. It takes exactly one argument. With the `string` engine it does no sanitization; use `html5lib`/`lxml` if you need that.
309
+ 3. **Engine output is not byte-identical across engines.** Switching engines produces real differences (attribute order, quote escaping, self-closing tags, attribute-name validation). Update snapshot tests when changing `engine`.
310
+ 4. **Overlapping inline styles render with minimum tags** (e.g. `<strong>Bold <em>Italic</em></strong>` rather than reopening `<strong>`). Semantically equivalent but breaks tests asserting exact strings.
311
+ 5. **`style` props accept a dict** (camelCase keys) converted to a CSS string. Properties keep insertion order — not sorted alphabetically. Pass `style` as a string for byte-stable output.
312
+ 6. **`className` is not auto-converted to `class`.** Use `class` directly.
313
+ 7. **Engine constants are dotted-path strings, not classes.** The exporter imports the class lazily at runtime via `import_string`.
314
+ 8. **`unstyled` blocks without text render as empty elements** (`<p></p>`), not nothing.
315
+
316
+ ## Public API
317
+
318
+ All imported from `draftjs_exporter` directly:
319
+
320
+ - **`HTMLExporter`** — `HTMLExporter(config).render(content_state)`.
321
+ - **`DOM`** — facade over the active engine. `create_element`, `render`, `render_debug`, `parse_html`, `append_child`, `camel_to_dash`. Engine constants: `DOM.STRING`, `DOM.HTML5LIB`, `DOM.LXML`, `DOM.STRING_COMPAT`, `DOM.MARKDOWN`.
322
+ - **Default maps & configs**: `BLOCK_MAP`, `STYLE_MAP`, `HTML_CONFIG`, `MARKDOWN_CONFIG`.
323
+ - **Default components**: `code_block` (`pre` > `code`), `render_children` (passthrough; used for atomic blocks).
324
+ - **Constants**: `BLOCK_TYPES`, `INLINE_STYLES`, `ENTITY_TYPES` (each with a `FALLBACK` member).
325
+ - **Markdown helper**: `build_markdown_config(options)`, plus the option type alias `MarkdownOptions`.
326
+ - **Markdown components**: `md_block`, `md_inline`, `md_mark_safe`, `md_link_destination`, `md_link`, `md_image`, `md_prefixed_block`, `md_make_ul` / `md_ul`, `md_make_ol` / `md_ol`, `md_list_wrapper`, `md_code_element` / `md_code_wrapper`, `md_inline_style`, `md_code_span`, `md_horizontal_rule` / `md_make_horizontal_rule`, `md_*_fallback`.
327
+ - **Importer**: `MarkdownImporter(config).import_markdown(markdown)` — converts Markdown to ContentState. Config keys: `parser` (dotted path), `parser_config` (feature toggles, `link_resolvers`/`image_resolvers`, `inline_html_styles`), `filter_rules`.
328
+ - **Parser**: `MarkdownParser(config).parse(markdown)`, `ParserConfig`, `scheme_resolver(scheme, type_map, coerce, label_key, mutability)`, `EntityResolver`, `EntityResolution`.
329
+ - **Filter**: `ContentStateFilter(rules).apply(content_state)`, `FilterRule` (`type`: `block`/`inline_style`/`entity`; `match`; `action`: `remove`/`keep`/`demote`/callable).
330
+ - **Errors**: `MarkdownParseError` (with `.line` and `.message`).
331
+ - **Type aliases**: `Props`, `Element`, `Component`, `ContentState`, `Block`, `Entity`, `EntityMap`, `EntityRange`, `InlineStyleRange`, `RenderableConfig`, `ExporterConfig`, plus internals (`CompositeDecorators`, `ConfigMap`, `Decorator`, `EntityKey`, `Mutability`, `RenderableType`, `Tag`).
332
+ - **`DOMEngine`** — abstract base for custom engines, re-exported at the top level.
333
+
334
+ For every `BLOCK_TYPES.*`, `INLINE_STYLES.*`, `ENTITY_TYPES.*` value, see [the API reference](https://wagtail.github.io/draftjs_exporter/api/) or [`constants.py`](https://github.com/wagtail/draftjs_exporter/blob/main/draftjs_exporter/constants.py).
335
+
336
+ ## Resources
337
+
338
+ - [Full docs site](https://wagtail.github.io/draftjs_exporter/)
339
+ - [llms-full.txt](https://wagtail.github.io/draftjs_exporter/llms-full.txt)
340
+ - [GitHub](https://github.com/wagtail/draftjs_exporter)
@@ -0,0 +1,194 @@
1
+ """Public API and metadata of draftjs_exporter.
2
+
3
+ This module gathers metadata, core classes, default configuration maps,
4
+ type aliases, and helpers so callers can import everything from
5
+ ``draftjs_exporter`` directly.
6
+ """
7
+
8
+ __title__ = "draftjs_exporter"
9
+ __version__ = "7.2.0"
10
+ __uri__ = "https://wagtail.github.io/draftjs_exporter/"
11
+ __author__ = "Springload and Contributors"
12
+ __license__ = "MIT"
13
+ __copyright__ = "Copyright 2016-present Springload and Contributors"
14
+
15
+ from draftjs_exporter.constants import BLOCK_TYPES as BLOCK_TYPES
16
+ from draftjs_exporter.constants import ENTITY_TYPES as ENTITY_TYPES
17
+ from draftjs_exporter.constants import INLINE_STYLES as INLINE_STYLES
18
+ from draftjs_exporter.contentstate_filter import (
19
+ ContentStateFilter as ContentStateFilter,
20
+ )
21
+ from draftjs_exporter.contentstate_filter import FilterRule as FilterRule
22
+ from draftjs_exporter.defaults import BLOCK_MAP as BLOCK_MAP
23
+ from draftjs_exporter.defaults import STYLE_MAP as STYLE_MAP
24
+ from draftjs_exporter.defaults import code_block as code_block
25
+ from draftjs_exporter.defaults import render_children as render_children
26
+ from draftjs_exporter.dom import DOM as DOM
27
+ from draftjs_exporter.engines.base import DOMEngine as DOMEngine
28
+ from draftjs_exporter.entity_state import (
29
+ ExporterEntityException as ExporterEntityException,
30
+ )
31
+ from draftjs_exporter.error import MarkdownParseError as MarkdownParseError
32
+ from draftjs_exporter.html import HTML as HTML
33
+ from draftjs_exporter.html import ExporterConfig as ExporterConfig
34
+ from draftjs_exporter.markdown import CONFIG as MARKDOWN_CONFIG
35
+ from draftjs_exporter.markdown import MarkdownOptions as MarkdownOptions
36
+ from draftjs_exporter.markdown import build_markdown_config as build_markdown_config
37
+ from draftjs_exporter.markdown.blocks import md_blockquote as md_blockquote
38
+ from draftjs_exporter.markdown.blocks import md_list_wrapper as md_list_wrapper
39
+ from draftjs_exporter.markdown.blocks import md_make_ol as md_make_ol
40
+ from draftjs_exporter.markdown.blocks import md_make_ul as md_make_ul
41
+ from draftjs_exporter.markdown.blocks import md_ol as md_ol
42
+ from draftjs_exporter.markdown.blocks import md_prefixed_block as md_prefixed_block
43
+ from draftjs_exporter.markdown.blocks import md_ul as md_ul
44
+ from draftjs_exporter.markdown.code import md_code_element as md_code_element
45
+ from draftjs_exporter.markdown.code import md_code_wrapper as md_code_wrapper
46
+ from draftjs_exporter.markdown.code import md_make_code_element as md_make_code_element
47
+ from draftjs_exporter.markdown.code import md_make_code_wrapper as md_make_code_wrapper
48
+ from draftjs_exporter.markdown.entities import md_horizontal_rule as md_horizontal_rule
49
+ from draftjs_exporter.markdown.entities import md_image as md_image
50
+ from draftjs_exporter.markdown.entities import md_link as md_link
51
+ from draftjs_exporter.markdown.entities import (
52
+ md_make_horizontal_rule as md_make_horizontal_rule,
53
+ )
54
+ from draftjs_exporter.markdown.escape import (
55
+ md_escape_link_destination as md_escape_link_destination,
56
+ )
57
+ from draftjs_exporter.markdown.escape import md_escape_text as md_escape_text
58
+ from draftjs_exporter.markdown.fallbacks import md_block_fallback as md_block_fallback
59
+ from draftjs_exporter.markdown.fallbacks import md_entity_fallback as md_entity_fallback
60
+ from draftjs_exporter.markdown.fallbacks import md_style_fallback as md_style_fallback
61
+ from draftjs_exporter.markdown.helpers import md_block as md_block
62
+ from draftjs_exporter.markdown.helpers import md_inline as md_inline
63
+ from draftjs_exporter.markdown.helpers import md_link_destination as md_link_destination
64
+ from draftjs_exporter.markdown.helpers import md_mark_safe as md_mark_safe
65
+ from draftjs_exporter.markdown.lists import md_make_list_item as md_make_list_item
66
+ from draftjs_exporter.markdown.styles import md_code_span as md_code_span
67
+ from draftjs_exporter.markdown.styles import md_inline_style as md_inline_style
68
+ from draftjs_exporter.markdown_importer import ImporterConfig as ImporterConfig
69
+ from draftjs_exporter.markdown_importer import MarkdownImporter as MarkdownImporter
70
+ from draftjs_exporter.markdown_parser import EntityResolution as EntityResolution
71
+ from draftjs_exporter.markdown_parser import EntityResolver as EntityResolver
72
+ from draftjs_exporter.markdown_parser import MarkdownParser as MarkdownParser
73
+ from draftjs_exporter.markdown_parser import ParserConfig as ParserConfig
74
+ from draftjs_exporter.markdown_parser import scheme_resolver as scheme_resolver
75
+ from draftjs_exporter.types import Block as Block
76
+ from draftjs_exporter.types import Component as Component
77
+ from draftjs_exporter.types import CompositeDecorators as CompositeDecorators
78
+ from draftjs_exporter.types import ConfigMap as ConfigMap
79
+ from draftjs_exporter.types import ContentState as ContentState
80
+ from draftjs_exporter.types import Decorator as Decorator
81
+ from draftjs_exporter.types import Element as Element
82
+ from draftjs_exporter.types import Entity as Entity
83
+ from draftjs_exporter.types import EntityKey as EntityKey
84
+ from draftjs_exporter.types import EntityMap as EntityMap
85
+ from draftjs_exporter.types import EntityRange as EntityRange
86
+ from draftjs_exporter.types import InlineStyleRange as InlineStyleRange
87
+ from draftjs_exporter.types import Mutability as Mutability
88
+ from draftjs_exporter.types import Props as Props
89
+ from draftjs_exporter.types import RenderableConfig as RenderableConfig
90
+ from draftjs_exporter.types import RenderableType as RenderableType
91
+ from draftjs_exporter.types import Tag as Tag
92
+
93
+ __all__ = [
94
+ # Metadata
95
+ "__title__",
96
+ "__version__",
97
+ "__uri__",
98
+ "__author__",
99
+ "__license__",
100
+ "__copyright__",
101
+ # Core
102
+ "Exporter",
103
+ "HTML",
104
+ "HTMLExporter",
105
+ "ExporterConfig",
106
+ "ContentState",
107
+ "DOM",
108
+ "DOMEngine",
109
+ "ExporterEntityException",
110
+ # Configs
111
+ "HTML_CONFIG",
112
+ "MARKDOWN_CONFIG",
113
+ "MarkdownOptions",
114
+ "build_markdown_config",
115
+ # Markdown helpers
116
+ "md_block",
117
+ "md_block_fallback",
118
+ "md_blockquote",
119
+ "md_code_element",
120
+ "md_code_span",
121
+ "md_code_wrapper",
122
+ "md_entity_fallback",
123
+ "md_escape_link_destination",
124
+ "md_escape_text",
125
+ "md_horizontal_rule",
126
+ "md_image",
127
+ "md_inline",
128
+ "md_inline_style",
129
+ "md_link",
130
+ "md_link_destination",
131
+ "md_list_wrapper",
132
+ "md_make_code_element",
133
+ "md_make_code_wrapper",
134
+ "md_make_horizontal_rule",
135
+ "md_make_list_item",
136
+ "md_make_ol",
137
+ "md_make_ul",
138
+ "md_mark_safe",
139
+ "md_ol",
140
+ "md_prefixed_block",
141
+ "md_style_fallback",
142
+ "md_ul",
143
+ # Importer
144
+ "MarkdownImporter",
145
+ "ImporterConfig",
146
+ "MarkdownParser",
147
+ "ParserConfig",
148
+ "ContentStateFilter",
149
+ "FilterRule",
150
+ "MarkdownParseError",
151
+ "EntityResolution",
152
+ "EntityResolver",
153
+ "scheme_resolver",
154
+ # Constants
155
+ "BLOCK_TYPES",
156
+ "ENTITY_TYPES",
157
+ "INLINE_STYLES",
158
+ # Defaults
159
+ "BLOCK_MAP",
160
+ "STYLE_MAP",
161
+ "code_block",
162
+ "render_children",
163
+ # Types
164
+ "Block",
165
+ "Component",
166
+ "CompositeDecorators",
167
+ "ConfigMap",
168
+ "ContentState",
169
+ "Decorator",
170
+ "Element",
171
+ "Entity",
172
+ "EntityKey",
173
+ "EntityMap",
174
+ "EntityRange",
175
+ "InlineStyleRange",
176
+ "Mutability",
177
+ "Props",
178
+ "RenderableConfig",
179
+ "RenderableType",
180
+ "Tag",
181
+ ]
182
+
183
+ Exporter = HTML
184
+ """Compatibility alias for the HTML exporter."""
185
+
186
+ HTMLExporter = HTML
187
+ """Compatibility alias for the HTML exporter."""
188
+
189
+ HTML_CONFIG: ExporterConfig = {
190
+ "block_map": BLOCK_MAP,
191
+ "style_map": STYLE_MAP,
192
+ "engine": DOM.STRING,
193
+ }
194
+ """Default exporter configuration using the built-in maps and string engine."""
@@ -38,7 +38,7 @@ class Command:
38
38
  Returns:
39
39
  A list of start_entity and stop_entity commands.
40
40
  """
41
- commands: list["Command"] = []
41
+ commands: list[Command] = []
42
42
  for r in block.get("entityRanges", []):
43
43
  # Entity key is an integer in entity ranges, while a string in the entity map.
44
44
  data = str(r["key"])
@@ -59,7 +59,7 @@ class Command:
59
59
  Returns:
60
60
  A list of start_inline_style and stop_inline_style commands.
61
61
  """
62
- commands: list["Command"] = []
62
+ commands: list[Command] = []
63
63
  for r in block.get("inlineStyleRanges", []):
64
64
  data = r["style"]
65
65
  start = r["offset"]
@@ -29,7 +29,7 @@ def get_decorations(
29
29
  A sorted list of decorations as ``(start, end, match, decorator)`` tuples.
30
30
  """
31
31
  occupied: dict[int, int] = {}
32
- decorations = []
32
+ decorations: list[tuple[int, int, Any, Decorator]] = []
33
33
 
34
34
  for decorator in decorators:
35
35
  for match in decorator["strategy"].finditer(text):
@@ -49,7 +49,7 @@ def apply_decorators(
49
49
  text: str,
50
50
  block: Block,
51
51
  blocks: list[Block],
52
- ) -> Generator[str, None, None]:
52
+ ) -> Generator[str | Element, None, None]:
53
53
  """Yield decorated text segments and decorator elements for a block.
54
54
 
55
55
  Parameters: