Metadata-Version: 2.5
Name: mistune-json
Version: 0.3.0
Summary: A JSON renderer for the Mistune Markdown parser
Project-URL: homepage, https://github.com/fernandonino/mistune-json
Author-email: fernandonino <nino.fernando@gmail.com>
License-Expression: MIT
Keywords: converter,json,markdown,mistune,parser,renderer
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.12
Requires-Dist: mistune<4.0.0,>=3.1.3
Description-Content-Type: text/markdown

# Mistune JSON

A JSON renderer for the [Mistune](https://github.com/lepture/mistune) Markdown parser.

## Supported HTML elements

So far, the HTML elements supported by this renderer are limited to:

* Paragraph, `<p>`
* Image, `<img>`
* Ordered lists, `<ol>`
* Unordered lists, `<ul>`
* All heading elements, `<h1>` through `<h6>`
* Links, `<a>`
* Emphasis, `<em>`
* Strong, `<strong>`
* Blockquote, `<blockquote>`
* Line breake, `<br>`
* Thematic breake, `<hr>`
* Both inline and block code
* Generic directives (as `directive` nodes; parsing is a plugin's job)

## How to use the JSON renderer

### Installation

The package is published in PyPI, so it can be installed through `pip` (as any other package):

```shell
pip install mistune-json
```

### Usage

```python
import mistune
from mistune_json import JsonRenderer

# Create a renderer instance
renderer = JsonRenderer()

# Create a Markdown parser with the JSON renderer
markdown = mistune.create_markdown(renderer=renderer)

# Parse Markdown text
result = markdown("# Hello, world!")

print(result)
# {'content': [{'type': 'h', 'content': [{'type': 'text', 'content': 'Hello, world!'}], 'level': 1}]}
```

## Output Schema

The JSON output is a dictionary with a `content` key containing a list of nodes. Each node has a `type` field identifying its kind.

### Node Types

| Type | Description | Fields |
|------|-------------|--------|
| `text` | Plain text | `content` (str) |
| `p` | Paragraph | `content` (Node[]) |
| `h` | Heading (h1-h6) | `content` (Node[]), `level` (int 1-6) |
| `code` | Code block (fenced) | `content` (str), `lang` (str, optional) |
| `codespan` | Inline code | `content` (str) |
| `blockquote` | Block quote | `content` (Node[]) |
| `ol` | Ordered list | `content` (Node[], of `item`), `start` (int, optional) |
| `ul` | Unordered list | `content` (Node[], of `item`) |
| `item` | List item | `content` (Node[]) |
| `a` | Link | `content` (Node[]), `href` (str), `title` (str, optional) |
| `img` | Image | `src` (str), `alt` (str), `title` (str, optional) |
| `em` | Emphasis (italic) | `content` (Node[]) |
| `strong` | Strong (bold) | `content` (Node[]) |
| `directive` | Generic directive | `name` (str), `attrs` (object), `content` (Node[]) |
| `hr` | Thematic break (horizontal rule) | - |
| `br` | Line break | - |

`content` is a string only for text-bearing nodes (`text`, `code`, `codespan`); everywhere else it is
a list of child nodes. Two rules hold at every depth:

- **Blank lines produce no node.** Consecutive blank lines collapse.
- **A renderer method that returns a list is spliced into its parent.** This is what makes
  `block_text` (the paragraph Mistune uses inside a tight list) transparent, so list items get a flat
  `content` array rather than `[[...]]`.

Key order is not part of the contract; compare parsed JSON, not bytes.

### Example

Input:

```markdown
# Title

This is a paragraph with **bold** and *italic* text.

![alt text](image.png)

- Item 1
- Item 2
```

Output:

```json
{
  "content": [
    {"type": "h", "content": [{"type": "text", "content": "Title"}], "level": 1},
    {"type": "p", "content": [
      {"type": "text", "content": "This is a paragraph with "},
      {"type": "strong", "content": [{"type": "text", "content": "bold"}]},
      {"type": "text", "content": " and "},
      {"type": "em", "content": [{"type": "text", "content": "italic"}]},
      {"type": "text", "content": " text."}
    ]},
    {"type": "img", "src": "image.png", "alt": "alt text"},
    {"type": "ul", "content": [
      {"type": "item", "content": [{"type": "text", "content": "Item 1"}]},
      {"type": "item", "content": [{"type": "text", "content": "Item 2"}]}
    ]}
  ]
}
```

### Directives

A `directive` node is produced by a **parser plugin**, not by this package. The renderer only renames
the token's `children` to `content` and copies `options` into `attrs`:

```python
{"type": "directive", "name": "position", "attrs": {"from": "2021"}, "content": [...]}
```

A plugin must emit a token of this shape. `children` is always present, even when empty, and `name`
and the options ride inside `attrs` because Mistune's `render_token` forwards nothing else:

```python
{
    "type": "directive",
    "attrs": {"name": "position", "options": {"from": "2021"}},
    "children": [],
}
```

`JsonRenderer.NAME` is `"json"`, so Mistune's HTML-only directive plugins (admonition, figure,
include, toc) decline to register themselves against it.

## Limitations

The following elements are not yet supported:

* Tables (headers and rows)
* Definition lists
* Task lists

## Extensibility

You can subclass `JsonRenderer` to customize the JSON output by overriding two hooks:

### `create_node(node_type, data)`

Called for every node before it's returned. Override to add custom fields to nodes.

```python
from mistune_json import JsonRenderer


class CustomRenderer(JsonRenderer):
    def create_node(self, node_type, data):
        node = super().create_node(node_type, data)
        node["source"] = "mistune-json"  # Add source to all nodes
        return node
```

### `finalize_output(output)`

Called after all content is rendered. Override to transform the final output.

```python
from mistune_json import JsonRenderer


class CustomRenderer(JsonRenderer):
    def finalize_output(self, output):
        output["meta"] = {"generated_by": "my-app"}
        return output
```

### Full Example

```python
import mistune
from mistune_json import JsonRenderer
from datetime import datetime


class CustomRenderer(JsonRenderer):
    def create_node(self, node_type, data):
        node = super().create_node(node_type, data)
        node["rendered_at"] = datetime.now().isoformat()
        return node

    def finalize_output(self, output):
        output["meta"] = {"version": "1.0"}
        return output


renderer = CustomRenderer()
markdown = mistune.create_markdown(renderer=renderer)
result = markdown("# Hello")

# Each node now has 'rendered_at', and output has 'meta'
print(result)
```

## Contributing and Feedback

Contributions are welcome! Feel free to open issues for:

* **Enhancements** - Request new features or supported elements
* **Bugs** - Report problems with the JSON output structure

Repository: [https://github.com/fernandonino/mistune-json](https://github.com/fernandonino/mistune-json)
