slack-markdown-parser 2.0.2__tar.gz → 2.2.1__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 (22) hide show
  1. slack_markdown_parser-2.2.1/CHANGELOG.md +62 -0
  2. slack_markdown_parser-2.2.1/CONTRIBUTING.md +53 -0
  3. slack_markdown_parser-2.2.1/MANIFEST.in +10 -0
  4. {slack_markdown_parser-2.0.2/slack_markdown_parser.egg-info → slack_markdown_parser-2.2.1}/PKG-INFO +17 -8
  5. slack_markdown_parser-2.2.1/README-ja.md +154 -0
  6. {slack_markdown_parser-2.0.2 → slack_markdown_parser-2.2.1}/README.md +16 -7
  7. slack_markdown_parser-2.2.1/docs/spec-ja.md +160 -0
  8. slack_markdown_parser-2.2.1/docs/spec.md +159 -0
  9. {slack_markdown_parser-2.0.2 → slack_markdown_parser-2.2.1}/pyproject.toml +4 -1
  10. {slack_markdown_parser-2.0.2 → slack_markdown_parser-2.2.1}/slack_markdown_parser/__init__.py +7 -1
  11. {slack_markdown_parser-2.0.2 → slack_markdown_parser-2.2.1}/slack_markdown_parser/converter.py +294 -50
  12. slack_markdown_parser-2.2.1/slack_markdown_parser/py.typed +1 -0
  13. {slack_markdown_parser-2.0.2 → slack_markdown_parser-2.2.1/slack_markdown_parser.egg-info}/PKG-INFO +17 -8
  14. {slack_markdown_parser-2.0.2 → slack_markdown_parser-2.2.1}/slack_markdown_parser.egg-info/SOURCES.txt +10 -1
  15. slack_markdown_parser-2.2.1/tests/fixtures/llm_markdown_p0_corpus.md +121 -0
  16. {slack_markdown_parser-2.0.2 → slack_markdown_parser-2.2.1}/tests/test_converter.py +170 -2
  17. slack_markdown_parser-2.2.1/tests/test_llm_markdown_p0_corpus.py +49 -0
  18. {slack_markdown_parser-2.0.2 → slack_markdown_parser-2.2.1}/LICENSE +0 -0
  19. {slack_markdown_parser-2.0.2 → slack_markdown_parser-2.2.1}/setup.cfg +0 -0
  20. {slack_markdown_parser-2.0.2 → slack_markdown_parser-2.2.1}/slack_markdown_parser.egg-info/dependency_links.txt +0 -0
  21. {slack_markdown_parser-2.0.2 → slack_markdown_parser-2.2.1}/slack_markdown_parser.egg-info/requires.txt +0 -0
  22. {slack_markdown_parser-2.0.2 → slack_markdown_parser-2.2.1}/slack_markdown_parser.egg-info/top_level.txt +0 -0
@@ -0,0 +1,62 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on Keep a Changelog, and the project follows Semantic Versioning.
6
+
7
+ ## [Unreleased]
8
+
9
+ ### Added
10
+
11
+ - Added a contributor guide, issue templates, and a pull request template.
12
+ - Added the `py.typed` marker and packaging rules for docs and tests in source distributions.
13
+
14
+ ### Changed
15
+
16
+ - CI now runs the test suite on Python 3.10, 3.11, 3.12, and 3.13.
17
+ - `.gitignore` no longer ignores the entire `tests/` directory, so new tests and sample fixtures can be tracked.
18
+
19
+ ## [2.2.1] - 2026-03-07
20
+
21
+ ### Changed
22
+
23
+ - Normalized underscore emphasis (`_..._`, `__...__`) into Slack-compatible asterisk emphasis before table parsing.
24
+ - Excluded fenced code blocks from table normalization and segment splitting so table-like rows inside code fences stay in `markdown` blocks.
25
+ - Extended fenced-code preservation to tilde fences (`~~~ ... ~~~`) when inserting ZWSP.
26
+ - Improved heading-plus-table rescue so inputs like `### Heading ... Header A | Header B` preserve multi-word first header cells more naturally.
27
+
28
+ ## [2.0.2] - 2026-03-06
29
+
30
+ ### Changed
31
+
32
+ - Improved public API wording and refreshed the English and Japanese README files.
33
+ - Clarified project scope for `mrkdwn`-only clients and fallback text behavior.
34
+ - Refined validation samples and screenshots in the documentation.
35
+
36
+ ### Fixed
37
+
38
+ - Improved ZWSP padding around punctuation-adjacent Markdown.
39
+ - Expanded parser edge-case handling and related documentation.
40
+
41
+ ## [2.0.1] - 2026-03-05
42
+
43
+ ### Added
44
+
45
+ - Added initial GitHub Actions coverage for tests and package builds.
46
+ - Added OSS-facing documentation and a real rendering example in the README.
47
+
48
+ ### Changed
49
+
50
+ - Trimmed the repository to the core distributable package and refreshed maintainer contact details.
51
+ - Updated GitHub Actions dependencies to newer major versions.
52
+
53
+ ### Fixed
54
+
55
+ - Added ZWSP padding for inline code markers when adjacent to surrounding text.
56
+
57
+ ## [2.0.0] - 2026-03-05
58
+
59
+ ### Added
60
+
61
+ - Published the packaged OSS release of `slack-markdown-parser`.
62
+ - Added packaging metadata, documentation, and review/reference materials for the open-source release.
@@ -0,0 +1,53 @@
1
+ # Contributing
2
+
3
+ Thanks for your interest in improving `slack-markdown-parser`.
4
+ Small fixes, bug reports, documentation updates, and tests are all welcome.
5
+ English and Japanese contributions are both welcome.
6
+
7
+ ## Development setup
8
+
9
+ This project supports Python 3.10 through 3.13.
10
+
11
+ ```bash
12
+ python -m venv .venv
13
+ source .venv/bin/activate
14
+ python -m pip install --upgrade pip
15
+ pip install -e ".[dev]"
16
+ ```
17
+
18
+ ## Local checks
19
+
20
+ Run these commands before opening a pull request:
21
+
22
+ ```bash
23
+ python -m pytest -q
24
+ python -m ruff check .
25
+ python -m black --check .
26
+ python -m build
27
+ ```
28
+
29
+ If you are changing Markdown parsing behavior, please add or update automated tests in `tests/`.
30
+
31
+ ## Pull requests
32
+
33
+ - Keep changes focused and explain the user-facing impact.
34
+ - Add or update tests when parser behavior changes.
35
+ - Update `README.md`, `README-ja.md`, or `docs/spec*.md` when public behavior or examples change.
36
+ - Conventional Commits are encouraged for commit messages, but not required for contributors.
37
+ - Make sure CI passes before requesting review.
38
+
39
+ ## Reporting issues
40
+
41
+ Use the GitHub issue templates when possible.
42
+ Helpful reports usually include:
43
+
44
+ - A short description of the problem
45
+ - A minimal Markdown sample that reproduces it
46
+ - Expected output and actual output
47
+ - Python version and installation method
48
+ - Slack rendering details if the issue is UI-specific
49
+
50
+ ## Release notes
51
+
52
+ Project maintainers keep release history in `CHANGELOG.md`.
53
+ If your pull request affects users, please include a short changelog note in the PR description.
@@ -0,0 +1,10 @@
1
+ include LICENSE
2
+ include README.md
3
+ include README-ja.md
4
+ include CONTRIBUTING.md
5
+ include CHANGELOG.md
6
+ recursive-include docs *.md
7
+ prune docs/_internal
8
+ recursive-include tests *.py *.md
9
+ recursive-include slack_markdown_parser py.typed
10
+ global-exclude __pycache__ *.py[cod]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: slack-markdown-parser
3
- Version: 2.0.2
3
+ Version: 2.2.1
4
4
  Summary: Convert LLM Markdown into Slack Block Kit markdown/table messages
5
5
  Author: darkgaldragon
6
6
  License-Expression: MIT
@@ -57,8 +57,11 @@ This library leans on Slack Block Kit's `markdown` block for standard Markdown a
57
57
  - Convert Markdown tables into Slack `table` blocks
58
58
  - Repair common LLM table issues such as missing outer pipes, missing separator rows, mismatched column counts, and empty cells
59
59
  - Split output into multiple Slack messages when needed to satisfy Slack's "one table per message" constraint
60
+ - Sanitize ANSI/control characters and neutralize invalid Slack angle-bracket tokens before block generation
60
61
  - Add ZWSP around inline formatting tokens to reduce rendering issues outside fenced code blocks
62
+ - Support Markdown and Slack-style links inside table cells
61
63
  - Build fallback text for `chat.postMessage.text` from generated blocks
64
+ - Accept raw LLM Markdown without tightly constraining the model prompt, using best-effort sanitize and table repair before Slack delivery
62
65
 
63
66
  ## Requirements
64
67
 
@@ -75,8 +78,7 @@ pip install slack-markdown-parser
75
78
 
76
79
  ```python
77
80
  from slack_markdown_parser import (
78
- build_fallback_text_from_blocks,
79
- convert_markdown_to_slack_messages,
81
+ convert_markdown_to_slack_payloads,
80
82
  )
81
83
 
82
84
  markdown = """
@@ -88,11 +90,7 @@ markdown = """
88
90
  | UI | *In progress* |
89
91
  """
90
92
 
91
- for blocks in convert_markdown_to_slack_messages(markdown):
92
- payload = {
93
- "blocks": blocks,
94
- "text": build_fallback_text_from_blocks(blocks) or "report",
95
- }
93
+ for payload in convert_markdown_to_slack_payloads(markdown):
96
94
  print(payload)
97
95
  ```
98
96
 
@@ -146,6 +144,7 @@ Example Slack bot rendering (`markdown` + `table` blocks):
146
144
  | Function | Description |
147
145
  |---|---|
148
146
  | `convert_markdown_to_slack_messages(markdown_text) -> list[list[dict]]` | Convert Markdown into Slack messages already split around table blocks. |
147
+ | `convert_markdown_to_slack_payloads(markdown_text) -> list[dict]` | Convert Markdown into Slack-ready payloads with both `blocks` and fallback `text`. |
149
148
  | `convert_markdown_to_slack_blocks(markdown_text) -> list[dict]` | Convert Markdown into a flat Block Kit block list. |
150
149
  | `build_fallback_text_from_blocks(blocks) -> str` | Build fallback text suitable for `chat.postMessage.text`. |
151
150
  | `blocks_to_plain_text(blocks) -> str` | Convert blocks into plain text. |
@@ -157,6 +156,7 @@ Example Slack bot rendering (`markdown` + `table` blocks):
157
156
  | `normalize_markdown_tables(markdown_text) -> str` | Normalize Markdown table syntax before conversion. |
158
157
  | `add_zero_width_spaces_to_markdown(text) -> str` | Insert ZWSP around formatting tokens where Slack needs stronger boundaries. |
159
158
  | `decode_html_entities(text) -> str` | Decode HTML entities before parsing. |
159
+ | `sanitize_slack_text(text) -> str` | Remove ANSI/control noise and neutralize invalid Slack angle-bracket tokens. |
160
160
  | `strip_zero_width_spaces(text) -> str` | Remove ZWSP (`U+200B`) and BOM (`U+FEFF`) while preserving join-control characters such as ZWJ. |
161
161
 
162
162
  ### Lower-level exported helpers
@@ -179,6 +179,15 @@ These are also part of the public package surface:
179
179
  - Generating Slack `mrkdwn` strings
180
180
  - Supporting clients or MCP tools that can only send `mrkdwn`
181
181
 
182
+ ## Contributing
183
+
184
+ Contributions, bug reports, and documentation improvements are welcome.
185
+ Please read [CONTRIBUTING.md](CONTRIBUTING.md) before opening an issue or pull request.
186
+
187
+ ## Changelog
188
+
189
+ Version history is maintained in [CHANGELOG.md](CHANGELOG.md).
190
+
182
191
  ## Contact
183
192
 
184
193
  - GitHub Issues / Pull Requests
@@ -0,0 +1,154 @@
1
+ # slack-markdown-parser
2
+
3
+ LLM が生成する標準的な Markdown を Slack Block Kit(`markdown` + `table` ブロック)に変換する Python ライブラリです。
4
+
5
+ ## 背景
6
+
7
+ Slack で AI BOT を運用する場合、従来は Slack 独自の `mrkdwn` 形式に変換していましたが、以下の課題がありました。
8
+
9
+ - **変換コスト**: LLM は標準 Markdown を出力するため、`mrkdwn` に合わせる変換ロジックやプロンプト制御が必要
10
+ - **装飾崩れ**: 日本語のように語間スペースがない文では、`mrkdwn` の装飾記号(`*`, `~` など)が正しくレンダリングされず記号がそのまま露出することがあり、ロジックやプロンプトでの制御も難しい
11
+ - **テーブル非対応**: `mrkdwn` にはテーブル構文がなく、リストへ変換するなどの代替処理が必要
12
+
13
+ ## 設計方針
14
+
15
+ Slack Block Kit の `markdown` ブロック(標準 Markdown 構文をそのまま受け付ける)と `table` ブロックを活用し、上記の課題を解消します。
16
+
17
+ | 課題 | 解決手段 |
18
+ |---|---|
19
+ | 変換コスト | `markdown` ブロックが標準 Markdown をそのまま受け付けるため、LLM 出力を変換せず利用可能 |
20
+ | 装飾崩れ | 装飾記号の前後に ZWSP(ゼロ幅スペース U+200B)を自動挿入してレンダリングを安定化。通常の半角スペースではなくゼロ幅スペースを使うことで、見た目上の不自然な空白を生じさせずに Slack の装飾解析を補助する |
21
+ | テーブル非対応 | Markdown テーブルを検知して `table` ブロックに変換。LLM の出力する多様なテーブル記法の揺れも自動補完し `invalid_blocks` エラーを回避 |
22
+
23
+ ## 主な機能
24
+
25
+ - 標準 Markdown テキストを `markdown` ブロックに変換
26
+ - Markdown テーブルを `table` ブロックに変換(セル内の太字・斜体・取消線・インラインコードを認識)
27
+ - LLM が生成する多様な Markdown テーブルで起こり得る記法の揺れ(外枠パイプ不足、セパレータ行不足、列数不一致、空セル)を検知し自動補完。Slack `table` ブロックの `invalid_blocks` エラーを未然に回避
28
+ - テーブルごとにメッセージを自動分割(Slack の「1メッセージ1テーブル」制約に対応)
29
+ - ANSI escape / 制御文字を除去し、不正な Slack 角括弧トークンを自動で無害化
30
+ - 装飾記号の前後に ZWSP を付与して表示崩れを抑制(フェンスドコードブロック内は除外、インラインコードは付与対象)
31
+ - テーブルセル内の Markdown link / Slack link を認識
32
+ - `chat.postMessage.text` 用の fallback テキストを生成(ブロック内容をプレーンテキスト化して通知プレビュー等に利用)
33
+ - モデル側で Markdown を厳密に制御しなくてもよいよう、Slack 送信前にベストエフォートでサニタイズとテーブル補完を行う
34
+
35
+ ## 利用前提
36
+
37
+ - Slack Block Kit の `blocks` で `markdown` / `table` ブロックを送信できる実装が必要です。
38
+ - `text` / `mrkdwn` のみ送信可能な経路(例: 一部の Slack MCP ツール)では利用できません。
39
+
40
+ ## インストール
41
+
42
+ ```bash
43
+ pip install slack-markdown-parser
44
+ ```
45
+
46
+ ## 最小利用例
47
+
48
+ ```python
49
+ from slack_markdown_parser import (
50
+ convert_markdown_to_slack_payloads,
51
+ )
52
+
53
+ markdown = """
54
+ # Weekly Report
55
+
56
+ | Team | Status |
57
+ |---|---|
58
+ | API | **On track** |
59
+ | UI | *In progress* |
60
+ """
61
+
62
+ for payload in convert_markdown_to_slack_payloads(markdown):
63
+ print(payload)
64
+ ```
65
+
66
+ `convert_markdown_to_slack_messages` は、複数テーブルを含む入力を Slack 制約に合わせて複数メッセージへ分割します。
67
+
68
+ ## 入出力イメージ
69
+
70
+ 検証テキスト:
71
+
72
+ ````markdown
73
+ # 週次プロダクト更新
74
+
75
+ 今週は**検索速度改善**と*UI調整*を進めました。旧仕様は~~廃止予定~~です。
76
+ 詳細ログIDは`run-20260305-02`です。
77
+ 参考: https://example.com/changelog
78
+
79
+ - APIの**レスポンス改善**
80
+ - *キャッシュヒット率*を改善
81
+ - タイムアウト設定を調整
82
+ - バッチ処理の安定化
83
+ - リトライ回数を統一
84
+ - ドキュメント更新
85
+
86
+ カテゴリ | 状況 | 担当
87
+ API | **進行中** | Team A
88
+ UI | *確認中* | Team B
89
+ QA | ~~保留~~ | Team C
90
+
91
+ > 注意: 本番反映は 3/8 10:00 JST を予定
92
+
93
+ 1. リリースノート最終確認
94
+ 1. 変更点の表記統一
95
+ 2. 影響範囲の追記
96
+ 2. 監視アラートしきい値調整
97
+ 1. `warning`閾値の更新
98
+ 3. QA再確認
99
+
100
+ ```bash
101
+ ./deploy.sh production
102
+ ```
103
+ ````
104
+
105
+ 実際のSlack BOTでの表示例(`markdown` + `table` ブロック):
106
+
107
+ ![Slack BOT rendering example](Example_ja.png)
108
+
109
+ ## ライブラリの公開インターフェース
110
+
111
+ ### メイン関数(公開関数)
112
+
113
+ | 関数 | 説明 |
114
+ |---|---|
115
+ | `convert_markdown_to_slack_messages(markdown_text) → list[list[dict]]` | Markdown をテーブル分割済みのメッセージ群に変換(主要エントリポイント) |
116
+ | `convert_markdown_to_slack_payloads(markdown_text) → list[dict]` | `blocks` と fallback `text` を含む Slack 送信用 payload 群へ変換 |
117
+ | `convert_markdown_to_slack_blocks(markdown_text) → list[dict]` | Markdown を Block Kit ブロックのリストに変換 |
118
+ | `build_fallback_text_from_blocks(blocks) → str` | ブロックから `chat.postMessage.text` 用 fallback テキストを生成 |
119
+ | `blocks_to_plain_text(blocks) → str` | ブロックからプレーンテキストを生成 |
120
+
121
+ ### ユーティリティ関数(公開関数)
122
+
123
+ | 関数 | 説明 |
124
+ |---|---|
125
+ | `normalize_markdown_tables(markdown_text) → str` | テーブル記法を正規化(パイプ補完、セパレータ生成、列数調整) |
126
+ | `add_zero_width_spaces_to_markdown(text) → str` | 装飾記号の前後に ZWSP を挿入(フェンスドコードブロック内は除外) |
127
+ | `decode_html_entities(text) → str` | HTML エンティティをデコード |
128
+ | `sanitize_slack_text(text) → str` | ANSI / 制御文字を除去し、不正な Slack 角括弧トークンを無害化 |
129
+ | `strip_zero_width_spaces(text) → str` | ZWSP (U+200B) と BOM (U+FEFF) を除去(ZWJ 等の結合制御文字は保持) |
130
+
131
+ ## 仕様
132
+
133
+ - 挙動仕様: [docs/spec-ja.md](docs/spec-ja.md)
134
+ - 非対応:
135
+ - `mrkdwn` 文字列の生成
136
+ - `mrkdwn` のみ送信可能なクライアント/MCP ツール
137
+
138
+ ## コントリビュート
139
+
140
+ 不具合報告、ドキュメント改善、コードの提案を歓迎します。
141
+ Issue / Pull Request を作成する前に [CONTRIBUTING.md](CONTRIBUTING.md) を参照してください。
142
+
143
+ ## 変更履歴
144
+
145
+ リリース履歴は [CHANGELOG.md](CHANGELOG.md) で管理しています。
146
+
147
+ ## 連絡先
148
+
149
+ - GitHub Issue / Pull Request
150
+ - X: [@darkgaldragon](https://x.com/darkgaldragon)
151
+
152
+ ## ライセンス
153
+
154
+ MIT
@@ -26,8 +26,11 @@ This library leans on Slack Block Kit's `markdown` block for standard Markdown a
26
26
  - Convert Markdown tables into Slack `table` blocks
27
27
  - Repair common LLM table issues such as missing outer pipes, missing separator rows, mismatched column counts, and empty cells
28
28
  - Split output into multiple Slack messages when needed to satisfy Slack's "one table per message" constraint
29
+ - Sanitize ANSI/control characters and neutralize invalid Slack angle-bracket tokens before block generation
29
30
  - Add ZWSP around inline formatting tokens to reduce rendering issues outside fenced code blocks
31
+ - Support Markdown and Slack-style links inside table cells
30
32
  - Build fallback text for `chat.postMessage.text` from generated blocks
33
+ - Accept raw LLM Markdown without tightly constraining the model prompt, using best-effort sanitize and table repair before Slack delivery
31
34
 
32
35
  ## Requirements
33
36
 
@@ -44,8 +47,7 @@ pip install slack-markdown-parser
44
47
 
45
48
  ```python
46
49
  from slack_markdown_parser import (
47
- build_fallback_text_from_blocks,
48
- convert_markdown_to_slack_messages,
50
+ convert_markdown_to_slack_payloads,
49
51
  )
50
52
 
51
53
  markdown = """
@@ -57,11 +59,7 @@ markdown = """
57
59
  | UI | *In progress* |
58
60
  """
59
61
 
60
- for blocks in convert_markdown_to_slack_messages(markdown):
61
- payload = {
62
- "blocks": blocks,
63
- "text": build_fallback_text_from_blocks(blocks) or "report",
64
- }
62
+ for payload in convert_markdown_to_slack_payloads(markdown):
65
63
  print(payload)
66
64
  ```
67
65
 
@@ -115,6 +113,7 @@ Example Slack bot rendering (`markdown` + `table` blocks):
115
113
  | Function | Description |
116
114
  |---|---|
117
115
  | `convert_markdown_to_slack_messages(markdown_text) -> list[list[dict]]` | Convert Markdown into Slack messages already split around table blocks. |
116
+ | `convert_markdown_to_slack_payloads(markdown_text) -> list[dict]` | Convert Markdown into Slack-ready payloads with both `blocks` and fallback `text`. |
118
117
  | `convert_markdown_to_slack_blocks(markdown_text) -> list[dict]` | Convert Markdown into a flat Block Kit block list. |
119
118
  | `build_fallback_text_from_blocks(blocks) -> str` | Build fallback text suitable for `chat.postMessage.text`. |
120
119
  | `blocks_to_plain_text(blocks) -> str` | Convert blocks into plain text. |
@@ -126,6 +125,7 @@ Example Slack bot rendering (`markdown` + `table` blocks):
126
125
  | `normalize_markdown_tables(markdown_text) -> str` | Normalize Markdown table syntax before conversion. |
127
126
  | `add_zero_width_spaces_to_markdown(text) -> str` | Insert ZWSP around formatting tokens where Slack needs stronger boundaries. |
128
127
  | `decode_html_entities(text) -> str` | Decode HTML entities before parsing. |
128
+ | `sanitize_slack_text(text) -> str` | Remove ANSI/control noise and neutralize invalid Slack angle-bracket tokens. |
129
129
  | `strip_zero_width_spaces(text) -> str` | Remove ZWSP (`U+200B`) and BOM (`U+FEFF`) while preserving join-control characters such as ZWJ. |
130
130
 
131
131
  ### Lower-level exported helpers
@@ -148,6 +148,15 @@ These are also part of the public package surface:
148
148
  - Generating Slack `mrkdwn` strings
149
149
  - Supporting clients or MCP tools that can only send `mrkdwn`
150
150
 
151
+ ## Contributing
152
+
153
+ Contributions, bug reports, and documentation improvements are welcome.
154
+ Please read [CONTRIBUTING.md](CONTRIBUTING.md) before opening an issue or pull request.
155
+
156
+ ## Changelog
157
+
158
+ Version history is maintained in [CHANGELOG.md](CHANGELOG.md).
159
+
151
160
  ## Contact
152
161
 
153
162
  - GitHub Issues / Pull Requests
@@ -0,0 +1,160 @@
1
+ # パーサー挙動仕様
2
+
3
+ このドキュメントは `slack-markdown-parser` の変換挙動を定義します。
4
+
5
+ ## 入力
6
+
7
+ - UTF-8 の Markdown 文字列
8
+ - 不正形テーブル、HTML エンティティ、プレーンテキスト混在を許容
9
+
10
+ ## 出力
11
+
12
+ - Slack Block Kit ブロック(`markdown` / `table`)
13
+ - 複数テーブル入力時は「1メッセージ1テーブル」を満たすメッセージ群
14
+
15
+ ## 変換パイプライン
16
+
17
+ `convert_markdown_to_slack_blocks` の処理順序:
18
+
19
+ 1. **HTML エンティティデコード** — `>`, `&` 等を元の文字に復元
20
+ 2. **Slack 向けサニタイズ** — ANSI / 制御文字を除去し、不正な Slack 角括弧トークンを無害化
21
+ 3. **underscore 装飾正規化** — `_..._` / `__...__` を Slack 互換の `*...*` / `**...**` に変換
22
+ 4. **テーブル正規化** — 後述のルールで不正形テーブルを補完
23
+ 5. **セグメント分割** — テーブル領域(`|...|` 行の連続)と非テーブル領域に分割
24
+ 6. **ブロック生成** — セグメント種別ごとに処理:
25
+ - テーブル領域 → セル装飾を解析し `table` ブロックを生成。変換に失敗した場合(テーブル候補行が2行未満、パース結果が空など)は `markdown` ブロックにフォールバック
26
+ - 非テーブル領域 → ZWSP を付与して `markdown` ブロックを生成
27
+
28
+ `convert_markdown_to_slack_messages` は上記の結果を「1メッセージ1テーブル」制約に沿って分割します。
29
+ `convert_markdown_to_slack_payloads` は、同じ分割結果に `chat.postMessage.text` 用 fallback を付けた payload 群を返します。
30
+
31
+ ## Slack 向けサニタイズルール
32
+
33
+ `sanitize_slack_text` の挙動:
34
+
35
+ - ANSI escape を除去する
36
+ - 一般的な制御文字を除去する
37
+ - 有効な Slack 角括弧トークンは保持する
38
+ - 例: リンク、メンション、チャンネル参照、`<!here>`、`<!subteam^...>`、`<!date^...>`
39
+ - Slack の特殊記法として解釈できない `<foo>` のようなトークンは `<foo>` に変換して無害化する
40
+ - これには `<div>` や `<span>` のような生 HTML 風タグも含まれ、Slack 特殊記法として解釈されないよう自動で無害化する
41
+
42
+ ## underscore 装飾正規化ルール
43
+
44
+ `normalize_underscore_emphasis` の挙動:
45
+
46
+ - `_text_` を `*text*` に変換する
47
+ - `__text__` を `**text**` に変換する
48
+ - 変換対象は ASCII 英数字の単語境界に埋まっていない装飾用途の underscore に限定する
49
+ - `snake_case` のような識別子は保持する
50
+ - `\_escaped\_` は保持する
51
+ - bare URL、Markdown リンク、Slack angle token、インラインコード内の underscore は保持する
52
+ - フェンスドコードブロック(`` ``` `` / `~~~`)内の underscore は保持する
53
+
54
+ ## テーブル正規化ルール
55
+
56
+ LLM は外枠パイプの省略、セパレータ行の欠落、列数の不一致など多様なテーブル記法を生成します。これらをそのまま Slack `table` ブロックに渡すと `invalid_blocks` エラーになるため、`normalize_markdown_tables` で事前に補完します。
57
+
58
+ ### テーブル候補の判定
59
+
60
+ - 連続する `|` 含有行を候補としてバッファリング
61
+ - 以下のいずれかで「テーブル」と判定:
62
+ - セパレータ行(`|---|---|` 形式)を含む
63
+ - 2行以上のデータ行があり、列数が一致または差が1以内で、最大列数が2以上
64
+
65
+ ### 正規化処理
66
+
67
+ - 外枠パイプが欠けている行は `|...|` 形式に補完
68
+ - セパレータ行が欠けていればヘッダ直後に自動生成
69
+ - 各行の列数はヘッダ幅に合わせて不足分を空セルで補完、超過分を切り詰め
70
+ - 空セルは `table` ブロック生成時に `-` に置換
71
+ - `# 見出し |a|b|` 形式は見出し行とテーブル行に分離(見出し内のインラインコードに含まれるパイプは無視)
72
+ - `### Heading ... Header A | Header B` のように見出しと表ヘッダが1行に潰れている場合、次行の列形状を手がかりに first header cell を推定する
73
+ - フェンスドコードブロック(`` ``` `` / `~~~`)内の行はテーブル候補として扱わない
74
+
75
+ ### セル内パイプの保持
76
+
77
+ - Slack リンク `<url|text>` 内のパイプはセル区切りとして扱わない
78
+ - インラインコード `` `...` `` 内のパイプもセル区切りとして扱わない
79
+ - エスケープされたパイプ `\|` はセル区切りとして扱わず、表示時にバックスラッシュを除去して `|` として出力
80
+
81
+ ## テーブルセル装飾
82
+
83
+ セル内で次のインライン装飾を認識し、Slack `rich_text` 要素のスタイルに変換:
84
+
85
+ | 記法 | スタイル |
86
+ |---|---|
87
+ | `**text**` | bold |
88
+ | `*text*` | italic |
89
+ | `~~text~~` | strike |
90
+ | `` `text` `` | code |
91
+
92
+ 複数装飾のネストはプレーンテキストとして保持します。
93
+
94
+ 加えて、セル内では次のリンク記法を認識します。
95
+
96
+ | 記法 | 出力 |
97
+ |---|---|
98
+ | `[label](https://example.com)` | Slack rich-text link |
99
+ | `<https://example.com|label>` | Slack rich-text link |
100
+ | `<https://example.com>` | Slack rich-text link |
101
+
102
+ ## ZWSP(ゼロ幅スペース)付与ルール
103
+
104
+ `add_zero_width_spaces_to_markdown` の挙動:
105
+
106
+ ### 目的
107
+
108
+ 日本語等の語間スペースがない文で装飾記号が隣接文字と結合し、Slack のレンダリングが崩れる問題を回避します。通常の半角スペースではなくゼロ幅スペースを使うことで、見た目上の不自然な空白を生じさせずに Slack の装飾解析を補助します。
109
+
110
+ ### 対象パターン
111
+
112
+ 以下の装飾記号について、前後のどちらか一方でも隣接文字がスペース・タブ・改行・ZWSP でない場合、または行頭・行末に接している場合、装飾トークン全体を ZWSP(U+200B)で囲って Slack に独立した境界として認識させる:
113
+
114
+ - `` `code` `` — インラインコード
115
+ - `**bold**` — 太字
116
+ - `*italic*` — 斜体
117
+ - `~~strike~~` — 取消線
118
+
119
+ ### 除外範囲
120
+
121
+ - **フェンスドコードブロック**(`` ``` ... ``` `` および `~~~ ... ~~~`)内は一切変更しない
122
+ - インラインコード(`` `...` ``)は除外**しない**(付与対象)
123
+
124
+ ## ZWSP 除去ルール
125
+
126
+ `strip_zero_width_spaces` はテーブルセル生成時および fallback テキスト生成時に呼ばれ、本ライブラリが挿入した制御文字を除去します。
127
+
128
+ ### 除去対象
129
+
130
+ | コードポイント | 名称 | 除去理由 |
131
+ |---|---|---|
132
+ | U+200B | ZWSP(ゼロ幅スペース) | 本ライブラリが装飾安定化のために挿入したもの |
133
+ | U+FEFF | BOM(バイト順マーク) | エンコーディング由来の不要な制御文字 |
134
+
135
+ ### 除去しない文字
136
+
137
+ | コードポイント | 名称 | 保持理由 |
138
+ |---|---|---|
139
+ | U+200C | ZWNJ(ゼロ幅非接合子) | ペルシャ語・ヒンディー語等の語形制御に使用 |
140
+ | U+200D | ZWJ(ゼロ幅接合子) | 結合絵文字(👨‍💻 = 👨+ZWJ+💻)の接着剤として使用。除去すると絵文字が分解される |
141
+
142
+ ## fallback テキスト
143
+
144
+ `build_fallback_text_from_blocks` は `chat.postMessage.text` に設定する通知プレビュー用テキストを生成:
145
+
146
+ - `markdown` ブロック → ZWSP を除去したテキスト
147
+ - `table` ブロック → 各行のセルテキストを ` | ` で連結
148
+ - 各ブロックの出力を空行で区切って結合
149
+
150
+ テーブルセル内リンクの fallback テキストは、ラベルがあればラベル、なければ URL を使います。
151
+
152
+ ## 決定性
153
+
154
+ 同一入力に対して、環境差分に依存せず常に同一の出力を返します。
155
+
156
+ ## 非ゴール
157
+
158
+ - `mrkdwn` 文字列の生成
159
+ - 完全な Markdown AST の再現
160
+ - HTML レンダリング互換