Metadata-Version: 2.4
Name: slack-markdown-parser
Version: 2.3.1
Summary: Convert LLM Markdown into Slack Block Kit markdown/table messages
Author: darkgaldragon
License-Expression: MIT
Project-URL: Homepage, https://github.com/darkgaldragon/slack-markdown-parser
Project-URL: Source, https://github.com/darkgaldragon/slack-markdown-parser
Project-URL: Issues, https://github.com/darkgaldragon/slack-markdown-parser/issues
Keywords: slack,markdown,block-kit,table,llm
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: black>=24.0.0; extra == "dev"
Requires-Dist: build>=1.2.0; extra == "dev"
Requires-Dist: pip-audit>=2.7.0; extra == "dev"
Requires-Dist: pytest>=8.0.0; extra == "dev"
Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
Requires-Dist: ruff>=0.6.0; extra == "dev"
Requires-Dist: twine>=5.1.0; extra == "dev"
Dynamic: license-file

# slack-markdown-parser

`slack-markdown-parser` is a Python library that converts ordinary Markdown generated by LLMs into Slack Block Kit messages built from `markdown` and `table` blocks.

## Why this library exists

Many Slack AI bots have traditionally converted model output into Slack-specific `mrkdwn`, but that approach creates a few recurring problems:

- Extra conversion work: LLMs naturally generate ordinary Markdown, so `mrkdwn` usually needs extra conversion logic or stricter prompts.
- Unstable formatting in languages without spaces between words: in Japanese, Chinese, Korean, and similar writing systems, Slack can fail to interpret `*`, `~`, and related markers correctly, exposing the raw punctuation.
- No table syntax in `mrkdwn`: if you stay in the old format, Markdown tables need custom table rendering.

## Design approach

This library uses Slack Block Kit `markdown` blocks for regular Markdown and `table` blocks for tables.

| Problem | Approach |
|---|---|
| Extra conversion work | Send ordinary Markdown through Slack `markdown` blocks without rewriting it into `mrkdwn`. |
| Formatting instability | Prefer zero-width spaces (`U+200B`) around formatting markers, and only add visible spaces in a few language-specific cases where Slack still renders Japanese, Chinese, or Korean text incorrectly. |
| No table syntax in `mrkdwn` | Detect Markdown tables and convert them into Slack `table` blocks, including repair of common LLM-generated table inconsistencies. |

The goal is natural rendering on Slack, not full CommonMark or HTML fidelity.
If Slack itself does not support a construct in `markdown` blocks, this library prefers safe plain-text rendering or explicit `table` blocks over aggressive rewrites into old `mrkdwn`.

## Features

- Convert ordinary Markdown into Slack `markdown` blocks
- Convert Markdown tables into Slack `table` blocks
- Repair common LLM table issues such as missing outer pipes, missing separator rows, mismatched column counts, and empty cells
- Split output into multiple Slack messages when needed to satisfy Slack's "one table per message" constraint
- Remove ANSI/control characters and neutralize invalid Slack angle-bracket tokens before block generation
- Add zero-width spaces around inline formatting markers to reduce rendering issues outside fenced code blocks, while preserving English-like punctuation-only boundaries that Slack already renders reliably
- Add visible spaces for a small set of nested inline-code cases in dense Japanese, Chinese, and Korean text when zero-width spaces alone are not enough
- Support Markdown links and Slack-style links inside table cells
- Optionally keep blank lines visible inside Slack `markdown` blocks by inserting placeholder lines, while keeping preview text unchanged
- Build preview text for `chat.postMessage.text` from generated blocks, removing the invisible or temporary spacing that was added only to stabilize Slack rendering
- Accept raw LLM Markdown without tightly constraining the model prompt, using best-effort sanitize and table repair before Slack delivery

## How Slack behaved in testing

The library is built around how Slack actually renders `markdown` and `table` blocks in practice.

Slack updated its [`markdown` block docs](https://docs.slack.dev/reference/block-kit/blocks/markdown-block/)
and [changelog entry](https://docs.slack.dev/changelog/2026/03/06/block-kit-rich-text)
on March 6, 2026 to describe broader support for headings, dividers, task lists,
native Markdown tables, and syntax-highlighted code blocks. In the Slack Web
workspace used for this project's April 8, 2026 validation, raw `markdown`
blocks rendered those constructs natively, including distinct heading levels
for `#`, `##`, `###`, and setext headings.

Slack still controls the exact release timing and visual style of those newer
features. Treat newer raw Markdown rendering as behavior controlled by Slack,
not by this library, and verify it in your own workspace, client, and posting
path.

Reliable in current Slack rendering:

- `**bold**`, `*italic*`, `~~strike~~`, inline code, and fenced code blocks
- Bare URLs, `<https://...>` style links, Markdown links, reference-style links, and mailto links
- Bullet lists, ordered lists, task lists, and simple blockquotes
- Explicit Slack `table` blocks generated from Markdown tables
- In environments where Slack has enabled the newer renderer, raw Markdown headings, dividers, and tables inside `markdown` blocks

Known Slack-side limitations:

- Exact heading sizes and some newer raw Markdown features still depend on the Slack app, workspace, and release state
- Paragraph breaks inside `markdown` blocks currently get little or no extra vertical spacing in tested Slack Web clients, so blank lines can look visually collapsed
- Nested blockquotes are weaker than in full Markdown renderers
- Raw Markdown tables inside `markdown` blocks now render in some newer Slack environments, but explicit Slack `table` blocks remain the reliable option across workspaces and delivery paths
- Markdown image syntax does not become an embedded image in `markdown` blocks
- Math, raw HTML, HTML comments, `<details>`, admonition syntax, and Mermaid are rendered as plain text or code, not as rich features

What this library compensates for:

- Normalizes underscore emphasis (`_..._`, `__...__`) into Slack-friendly asterisk emphasis
- Wraps bare URLs into Slack-friendly `<https://...>` link form before sending `markdown` blocks
- Repairs malformed LLM-generated tables before converting them into Slack `table` blocks
- Keeps table-like rows inside fenced code blocks out of table normalization
- Optionally turns internal blank lines into placeholder lines that keep paragraphs visibly separated in Slack `markdown` blocks
- Neutralizes invalid Slack angle-bracket tokens such as raw HTML-like tags

## Requirements

- Your Slack integration must support Block Kit payloads with `markdown` and `table` blocks.
- This library does not help when your delivery path only accepts plain `text` or `mrkdwn` strings.

## Installation

```bash
pip install slack-markdown-parser
```

## Quick start

```python
from slack_markdown_parser import (
    convert_markdown_to_slack_payloads,
)

markdown = """
# Weekly Report

| Team | Status |
|---|---|
| API | **On track** |
| UI | *In progress* |
"""

for payload in convert_markdown_to_slack_payloads(
    markdown,
    preserve_visual_blank_lines=True,
):
    print(payload)
```

`convert_markdown_to_slack_messages` automatically splits output into multiple messages when the input contains multiple tables.
Set `preserve_visual_blank_lines=True` when you want the parser to compensate
for Slack's currently tight paragraph spacing inside `markdown` blocks.
The blank-line workaround is intentionally narrow: it skips table segments and
avoids inserting placeholder lines right before setext heading underlines or
reference-link definitions.

## Rendering example

Example input:

````markdown
# Weekly Product Update

This week we worked on **search performance** and *UI polish*. The old flow is ~~scheduled for removal~~.
The detailed log ID is `run-20260305-02`.
Reference: https://example.com/changelog

- Improved **API response time**
  - Increased *cache hit rate*
  - Adjusted timeout settings
- Stabilized batch processing
  - Unified retry counts
- Updated documentation

Category | Status | Owner
API | **In progress** | Team A
UI | *Under review* | Team B
QA | ~~On hold~~ | Team C

> Note: production release is scheduled for 2026-03-08 10:00 JST

1. Finalize release notes
   1. Unify change labels
   2. Add impact notes
2. Tune monitoring alert thresholds
   1. Update the `warning` threshold
3. Re-check QA

```bash
./deploy.sh production
```
````

Example Slack bot rendering (`markdown` + `table` blocks):

![Slack BOT rendering example](Example_en.png)

## Public API

### Main functions

| Function | Description |
|---|---|
| `convert_markdown_to_slack_messages(markdown_text, *, preserve_visual_blank_lines=False) -> list[list[dict]]` | Convert Markdown into Slack messages already split around table blocks. |
| `convert_markdown_to_slack_payloads(markdown_text, *, preserve_visual_blank_lines=False) -> list[dict]` | Convert Markdown into Slack-ready request data with both `blocks` and preview `text`. |
| `convert_markdown_to_slack_blocks(markdown_text, *, preserve_visual_blank_lines=False) -> list[dict]` | Convert Markdown into a flat Block Kit block list. |
| `build_fallback_text_from_blocks(blocks) -> str` | Build preview text suitable for `chat.postMessage.text`. |
| `blocks_to_plain_text(blocks) -> str` | Convert blocks into plain text. |

`preserve_visual_blank_lines=True` replaces internal blank lines in non-table
Markdown segments with lines that contain only a non-breaking space. Those
placeholder lines are removed again when generating preview plain text, so Slack
notifications and logs stay close to the original Markdown source.
The current implementation deliberately skips blank runs that sit immediately
before setext-heading underlines or reference-link definitions, because those
boundaries can change Markdown meaning in newer Slack Markdown rendering.

### Utility functions

| Function | Description |
|---|---|
| `normalize_markdown_tables(markdown_text) -> str` | Normalize Markdown table syntax before conversion. |
| `add_zero_width_spaces_to_markdown(text) -> str` | Insert zero-width spaces around formatting tokens where Slack needs stronger boundaries. |
| `decode_html_entities(text) -> str` | Decode HTML entities before parsing. |
| `sanitize_slack_text(text) -> str` | Remove ANSI/control noise and neutralize invalid Slack angle-bracket tokens. |
| `strip_zero_width_spaces(text) -> str` | Remove zero-width spaces (`U+200B`) and BOM (`U+FEFF`) while preserving join-control characters such as ZWJ. |

### Lower-level exported helpers

These are also part of the public package API:

- `add_zero_width_spaces`
- `convert_markdown_text_to_blocks`
- `extract_plain_text_from_table_cell`
- `markdown_table_to_slack_table`
- `parse_markdown_table`
- `split_blocks_by_table`
- `split_markdown_into_segments`

## Specification and scope

- Behavior spec: [docs/spec.md](docs/spec.md)
- Japanese behavior spec: [docs/spec-ja.md](docs/spec-ja.md)
- Non-goals:
  - Generating Slack `mrkdwn` strings
  - Supporting clients or MCP tools that can only send `mrkdwn`

## Contributing

Contributions, bug reports, and documentation improvements are welcome.
Please read [CONTRIBUTING.md](CONTRIBUTING.md) before opening an issue or pull request. Maintainer-facing Slack renderer QA notes are linked from there rather than treated as part of the end-user package docs.

## Changelog

Version history is maintained in [CHANGELOG.md](CHANGELOG.md).

## Contact

- GitHub Issues / Pull Requests
- X: [@darkgaldragon](https://x.com/darkgaldragon)

## License

MIT
