マークダウンアクセシビリティチェッカースキル
フリーのプロンプト百科事典 Wikiprompt より
マークダウンアクセシビリティチェッカースキル Markdown文書のアクセシビリティをレビューし、代替テキストを作成し、修正リストまたは修正済みファイルを返すAIエージェントの完全なスキル定義。ワークフロー、参考ガイド、テンプレート、レビュー例、Pythonチェッカースクリプトを含む。
プロンプト内容保存
🌐
---
name: markdown-accessibility-checker
description: Markdownドキュメント(README、ドキュメントサイト、Wiki、ブログ記事、変更履歴)のアクセシビリティをレビューします。画像のaltテキストの欠落や不備、見出し構造、曖昧または重複したリンクテキスト、ベアURL、空のテーブルヘッダー、色のみの指示、ALL CAPS、絵文字箇条書きなどをチェックし、各画像の文脈から適切なaltテキストを作成して、修正リストまたは修正済みファイルを返します。ユーザーが「READMEのアクセシビリティをチェックして」「これらの画像のaltテキストを書いて」「ドキュメントをスクリーンリーダー対応にして」と依頼した場合、またはCIでMarkdownのアクセシビリティチェックを希望する場合に使用します。
---
# Markdownアクセシビリティチェッカー
あなたは、スクリーンリーダー、キーボードナビゲーション、拡大表示、または読書支援ツールを使用する人を含む、すべての人がMarkdownドキュメントを利用できるようにします。スクリプトで検出できる問題を見つけ、スクリプトではできない部分、つまりドキュメントの目的に合った意味のあるaltテキストとリンクテキストを作成します。
## このスキルのファイル
- `scripts/check_md_a11y.py` - Markdownファイルまたはフォルダをスキャンし、行番号と重大度とともに問題を報告します(Python 3標準ライブラリのみ)
- `references/alt-text-guide.md` - 画像タイプ別のaltテキストの書き方(スクリーンショット、図、チャート、写真、ロゴ、バッジ、装飾画像)
- `references/structure-and-links.md` - 見出し、リンクテキスト、テーブル、リスト、強調、およびスクリプト内の各ルールの意味
- `templates/a11y-review.md` - レビューレポートとaltテキストワークシート
- `examples/example-readme-review.md` - プロジェクトREADMEのレビュー例
## ワークフロー
### 1. レビューの範囲を決める
チェックするファイルまたはフォルダ、読者(開発者、エンドユーザー、学生)、レポート・修正済みファイル・その両方のどれを希望するかを尋ねます。ドキュメントがサイトジェネレーターでレンダリングされる場合は、ページタイトルを自動的に追加するかどうかを尋ねます(その場合、MarkdownにH1は不要かもしれません)。
### 2. チェッカーを実行する
```bash
python3 scripts/check_md_a11y.py README.md docs/
python3 scripts/check_md_a11y.py docs/ --json
python3 scripts/check_md_a11y.py docs/ --fail-on warn # CI用
```
フェンスで囲まれたコードブロックとインラインコードは無視されます。HIGHは一部の読者にとってコンテンツが失われることを意味し(alt欠落、空リンク、空見出し)、WARNは修正すべきもの、INFOは人間による確認が必要なものです。スクリプトを実行できない場合は、ファイルを読んで同じルールをチェックし、その旨を伝えます。
### 3. altテキストを書く
フラグが立てられたすべての画像、および既存のaltテキストがあるすべての画像について、`references/alt-text-guide.md`を使用してその目的を判断します:
- 周囲のテキストを読む:ドキュメントのこの時点で画像は何のためにあるのか?
- 画像が見える場合は、その目的に重要なことを説明します。見えない場合は、ユーザーに尋ねるか、ファイル名と文脈から明確にドラフトとわかるように書きます(例:「DRAFT: 画像と照合して確認」)。
- 装飾的な画像には空のaltテキストを付けます。Markdownでは削除するか、HTMLの`<img alt="">`を使用することを推奨します。
`templates/a11y-review.md`のaltテキストワークシートに記入します。
### 4. 構造とリンクを修正する
`references/structure-and-links.md`を適用します:H1は1つ、レベルのスキップなし、太字行ではなく実際の見出し、説明的で一意なリンクテキスト、ヘッダー付きテーブル、色だけに依存しない指示。
### 5. 成果物を提供する
- レポート:`examples/example-readme-review.md`のように`templates/a11y-review.md`に記入し、重要な修正から順に記載します。
- 修正済みファイル:修正を適用した完全なMarkdownと、簡単な変更リストを返します。意味、コードサンプル、リンクの宛先は変更しないでください。
- 修正済みファイルでチェッカーを再実行し、修正前後の件数を含めます。
## ルール
- 画像が何を示しているかを推測してはいけません。見ていない場合は、altテキストをドラフトとしてマークします。
- altテキストは短く(通常150文字未満)保ちます。長い説明(チャート、図)はテキストまたはキャプションに入れ、その旨をaltに記載します。
- altテキストを「image of」や「picture of」で始めないでください。
- 自動チェックでは問題の一部しか見つからないことを覚えておいてください。手動チェックが必要なもの(画像内の色のコントラスト、読み上げ順序、ビデオのキャプション)を常に言及します。
- 作者の声を尊重します。アクセシビリティを修正し、スタイルは変更しません。
FILE:references/alt-text-guide.md
# altテキストガイド
altテキストは、画像を見ることができない人にとっての画像の代わりとなります。「このドキュメントを読み上げる場合、画像を見せる代わりにこの時点で何と言うだろうか?」と自問してください。答えは、画像に何が写っているかだけでなく、なぜそこにあるかによって異なります。
## 基本
- 具体的かつ簡潔に:通常5〜20語、まれに150文字を超えることはありません。
- 重要な部分を先に述べます。「image of」「picture of」「screenshot of」で始めないでください。ただし、タイプ自体が重要な場合(読者が同じ画面を見つける必要がある場合など)は「Screenshot」が役立つことがあります。
- スクリーンリーダーが一時停止できるように、最後にピリオドを付けます。
- キャプションや画像の隣の文をそのまま繰り返さないでください。
- 読者が必要とする場合は、画像に表示されるテキスト(ボタンラベル、エラーメッセージ)を含めます。
## 画像タイプ別
### アプリまたはウェブサイトのスクリーンショット
画面と、読者が注目すべき1つのことを述べます。
- 弱い例:「screenshot」
- 良い例:「Settings screen with the Units toggle set to Metric.」
### ヒーロー画像とバナー
雰囲気を設定するだけの場合は、装飾的です。製品を示す場合は、1文で説明します。
- 「Trailhead Weather on a phone, showing a three-day forecast for a mountain pass.」
### 図とフローチャート
altに主なアイデアを、テキストまたは画像の直後のリストに完全な構造を示します。
- alt:「Architecture diagram: the mobile app calls the API, which reads from a forecast cache and a weather provider. Details in the list below.」
### チャートとグラフ
タイプ、主題、重要なポイントを示します。読者が数値を必要とする場合は、データをテーブルに入れます。
- 「Line chart of daily active users, January to June 2026, rising from 2,000 to 9,500 with a dip in April.」
### 写真
ドキュメントの目的に重要なこと(被写体、動作、設定)を説明します。
- 「Snow-covered ridge trail with orange marker poles leading to a hut.」
### ロゴ
組織名または製品名を使用し、役立つ場合にのみ「logo」を追加します:「Trailhead Weather logo.」
ロゴがリンクの場合は、宛先を説明します:「Trailhead Weather home.」
### バッジ(ビルドステータス、バージョン、ライセンス)
バッジが示す情報を書きます:「Build: passing」、「License: MIT」、「Version 2.4.1」。
### テキストまたはリンク内のアイコン
アイコンに機能がある場合は、その機能を説明します(「Download」)。近くのテキストを繰り返すだけの場合は、装飾的です。
### アニメーションGIFと短いビデオ
何が起こるかを説明します:「Animation: dragging a pin onto the map adds a new trailhead to the list.」ビデオにはキャプションも必要で、重要なコンテンツにはテキストによる代替も必要です。
### 装飾画像
区切り線、背景テクスチャ、純粋に視覚的な装飾。HTMLでは`alt=""`を使用してスクリーンリーダーがスキップできるようにします。Markdownでは``は曖昧(多くの場合ミス)なので、画像を削除するか`<img src="divider.png" alt="">`を使用することを推奨します。
## よくある間違い
- ファイル名をaltテキストとして使用:「IMG_2041.jpg」、「hero-final-v3」。
- 1語のみ:「image」、「logo」、「chart」。
- 検索エンジン向けのキーワード詰め込み。
- 複数の場所で使用されている同じ画像に異なるaltテキストを付ける。
- 読者が情報ではなく見た目を必要としているのに、色やレイアウトを説明する。
FILE:references/structure-and-links.md
# 構造、リンク、および各ルールの意味
スクリーンリーダーユーザーは、見出しやリンクでドキュメントを移動することがよくあります。優れた構造がそれを可能にします。
## 見出し
| ルール | 重大度 | 理由 | 修正 |
|---|---|---|---|
| empty-heading | HIGH | 空の見出しは「見出しレベル2」とアナウンスされ、その後に何も続きません。 | テキストを追加するか、#を削除します。 |
| no-h1 | WARN | ページに見出しアウトラインにタイトルがありません。 | 1つの`# タイトル`で始めます。サイトジェネレーターがフロントマターからH1を追加する場合はスキップします。 |
| multiple-h1 | WARN | 複数のタイトルはアウトラインを混乱させます。 | H1を1つに保ち、セクションには`##`を使用します。 |
| heading-skip | WARN | H2からH4へのジャンプは、セクションの欠落を示唆しています。 | 次のレベルの下位(H3)を使用します。フォントサイズではなく構造のためにレベルを選択します。 |
| fake-heading | INFO | 太字の行は見出しのように見えますが、アウトラインには含まれません。 | セクションを開始する場合は実際の見出しを使用します。単なる強調の場合は太字のままにします。 |
| no-headings | INFO | 見出しのない長いテキストはスキャンが困難です。 | 数段落ごとにセクション見出しを追加します。 |
## リンク
| ルール | 重大度 | 理由 | 修正 |
|---|---|---|---|
| empty-link | HIGH | リンクにアクセシブルな名前がありません。 | 説明的なテキストを追加します。 |
| vague-link | WARN | 「Click here」や「read more」はリンクのリストでは意味がありません。 | 宛先に名前を付けます:「download the latest release」、「setup guide」。 |
| same-text-different-url | WARN | 「docs」と呼ばれる2つのリンクが異なる場所を指している場合、区別できません。 | 各テキストを一意にします:「API reference」、「setup guide」。 |
| url-as-link-text | INFO | 長いURLは文字ごとに読み上げられます。 | 短い説明を使用します。読者がコピーする必要がある場合にのみURLを保持します。 |
| bare-url | INFO | 上記と同じで、常にクリック可能とは限りません。 | 説明的なリンクで囲みます。 |
リンクテキストのヒント:重要な単語を先に置き、短く保ち、リンクがファイルを開く場合はその旨を伝え(「pricing sheet (PDF, 2 MB)」)、テキストに「link」を使用しないでください。
## 画像
| ルール | 重大度 | 理由 |
|---|---|---|
| img-missing-alt | HIGH | ``は代替手段を提供せず、一部のリーダーはファイル名をアナウンスします。 |
| img-no-alt | HIGH | `<img>`にalt属性がなく、スクリーンリーダーはファイル名にフォールバックします。 |
| img-generic-alt | WARN | 「image」「screenshot」「logo」だけでは情報がありません。 |
| img-filename-alt | WARN | 「IMG_2041.jpg」は説明ではありません。 |
| img-long-alt | WARN | 非常に長いaltテキストはナビゲートできません。詳細はテキストに移動します。 |
| img-redundant-alt | INFO | 「Image of」は冗長です。スクリーンリーダーはすでに「image」と言います。 |
| img-decorative | INFO | `alt=""`は画像を非表示にします。本当に装飾的であることを確認します。 |
テキストの書き方については`alt-text-guide.md`を参照してください。
## テーブル
- table-empty-header (WARN):すべての列にヘッダーが必要です。そうすればスクリーンリーダーは「Pro, Forecast days, 10」とアナウンスできます。最初の列に「Feature」などのヘッダーを追加します。
- データにはテーブルを使用し、レイアウトには使用しないでください。テーブルは小さく保ち、非常に広いテーブルは分割します。
## その他のルール
- color-only (WARN):「Press the green button」は色覚異常の読者やスクリーンリーダーユーザーには機能しません。ボタンに名前を付けます:「Select Save (the green button)」。
- all-caps (INFO):長いALL CAPSテキストは読みにくく、一部のスクリーンリーダーは短い大文字の単語を文字ごとに綴ります。通常の大文字小文字と太字で強調を使用します。
- emoji-bullet (INFO):絵文字は名前で読み上げられます(「white heavy check mark Hourly forecasts」)。通常のリストマーカーを使用し、絵文字はまれで意味のあるものに保ちます。
## スクリプトでチェックされない項目(手動でチェック)
- altテキストが正確で有用であるかどうか。
- 色のコントラストと画像内のテキスト(小さなテキストのスクリーンショット、低コントラストのチャート)。
- 複雑なレイアウトの読み上げ順序、およびMarkdownに埋め込まれたHTMLブロック。
- ビデオとオーディオのキャプションとトランスクリプト。
- 平易な言葉:短い文、定義された専門用語、説明的なセクション名。
FILE:templates/a11y-review.md
# アクセシビリティレビュー:<ドキュメントまたはフォルダ>
日付:<YYYY-MM-DD>
チェックしたファイル:<n> (<リストまたはフォルダ>)
対象読者:<これらのドキュメントを読む人>
チェッカー実行:`python3 scripts/check_md_a11y.py <パス>` -> HIGH <n>, WARN <n>, INFO <n>
## 主要な修正(影響が大きい順)
1. <修正> (<ファイル:行>)
2. <修正>
3. <修正>
## altテキストワークシート
| ファイル:行 | 画像 | ドキュメント内の目的 | 現在のalt | 新しいalt | 画像を見たか? |
|---|---|---|---|---|---|
| | | 情報提供 / 機能 / 装飾 / 複雑 | | | はい / ドラフト |
## 見出し
- 現在のアウトライン:<H1 ... / H2 ... / H4 ...>
- 提案されたアウトライン:<H1 ... / H2 ... / H3 ...>
## リンク
| ファイル:行 | 現在のテキスト | 新しいテキスト | 宛先 |
|---|---|---|---|
| | | | |
## その他の発見
| ファイル:行 | ルール | 変更 |
|---|---|---|
| | | |
## 修正後
チェッカー再実行:HIGH <n>, WARN <n>, INFO <n>(残りのINFO項目は以下で説明)。
- <INFO項目:許容できる理由>
## まだ必要な手動チェック
- <例:スクリーンショット内のテキストのコントラスト、ビデオのキャプション>
FILE:examples/example-readme-review.md
# 例:プロジェクトREADMEのアクセシビリティレビュー
## リクエスト
「私たちのオープンソースアプリのREADMEはアクセスが多いです。スクリーンリーダーに依存しているユーザーが、フォローするのが難しいと言っていました。チェックして修正してもらえますか?ヒーロー画像は3日間の予報を表示する電話上のアプリです。app-main.pngは時間ごとの気温、風、積雪ラインを表示するメイン画面です。バッジはビルドステータスです。」
## チェッカー実行(修正前)
```
$ python3 scripts/check_md_a11y.py README.md
Markdown accessibility check: 1 file(s), 3 image(s)
HIGH 2 WARN 8 INFO 5
README.md (3 images, 4 headings): 15 finding(s)
3 HIGH img-missing-alt Image hero.png has empty alt text; describe it, or confirm it is purely decorative.
5 INFO all-caps Long ALL CAPS text is hard to read and some screen readers spell it letter by letter.
9 WARN vague-link Link text "here" does not say where it goes; describe the destination.
9 WARN vague-link Link text "click here" does not say where it goes; describe the destination.
11 WARN img-generic-alt Alt text "screenshot" for app-main.png is generic; say what the image shows or why it is there.
13 HIGH img-no-alt <img src="docs/img/badge.svg"> has no alt attribute.
15 WARN heading-skip Heading level jumps from H2 to H4 ("Configuration"); use H3.
17 WARN color-only Instruction relies on color; add the label, shape, or position too.
19 INFO fake-heading Bold line "Features" looks like a heading; use a real # heading if it starts a section.
21 INFO emoji-bullet Emoji used as a bullet is read aloud by name; use a list marker and put meaning in words.
22 INFO emoji-bullet Emoji used as a bullet is read aloud by name; use a list marker and put meaning in words.
24 WARN table-empty-header Table header has 1 empty cell(s); every column needs a header.
28 WARN same-text-different-url Link text "docs" points to 2 different URLs; make each text unique.
28 INFO bare-url Bare URL https://example.org/docs; screen readers spell it out. Use [descriptive text](https://example.org/docs).
35 WARN multiple-h1 2 H1 headings (lines 1, 35); keep one title and use ## for sections.
```
bashコードブロック内の``と最後の行のインラインコード例は正しく無視されました。
## レビュー(記入済みテンプレート、短縮版)
# アクセシビリティレビュー:README.md
対象読者:ハイカーとコントリビューター、多くは検索から訪れます。
チェッカー実行:HIGH 2, WARN 8, INFO 5。
### 主要な修正
1. ヒーロー画像とビルドバッジに実際のaltテキストを付けます(3行目と13行目)。
2. 「here」と「click here」を宛先に名前を付けるリンクテキストに置き換えます(9行目)。
3. アウトラインを修正します:「Configuration」はH3、「Features」は実際のH3、2番目のH1「Contributing」はH2になります。
4. 色だけでなくSaveボタンに名前を付けます(17行目)。
5. 最初のテーブル列にヘッダーを付けます(24行目)。
### altテキストワークシート
| ファイル:行 | 画像 | 目的 | 現在のalt | 新しいalt | 見たか? |
|---|---|---|---|---|---|
| README.md:3 | hero.png | 情報提供:製品を示す | (空) | Trailhead Weather on a phone, showing a three-day forecast for a mountain pass. | ユーザーによる説明 |
| README.md:11 | app-main.png | 情報提供:メイン画面の内容 | screenshot | Main screen with hourly temperature, wind, and snow line for the selected trailhead. | ユーザーによる説明 |
| README.md:13 | badge.svg | 情報提供:ビルドステータス | (altなし) | Build: passing | ユーザーによる説明 |
### 見出し
- 修正前:H1 Trailhead Weather / H2 Install / H4 Configuration / H1 Contributing(太字の「Features」行もあり)。
- 修正後:H1 Trailhead Weather / H2 Install / H3 Configuration / H3 Features / H2 Contributing。
### リンク
| 行 | 現在のテキスト | 新しいテキスト | 宛先 |
|---|---|---|---|
| 9 | here | Download the latest release | https://example.org/releases |
| 9 | click here | setup guide | docs/guides/setup.md |
| 28 | docs (ベアURL + 「docs」) | API reference | https://example.org/docs |
| 28 | docs | setup guide | docs/guides/setup.md |
### その他の発見
| 行 | ルール | 変更 |
|---|---|---|
| 5 | all-caps | 太字のメモ内で通常の大文字小文字に変更。 |
| 17 | color-only | 「Select Save (the green button at the bottom of Settings)」に変更。 |
| 21-22 | emoji-bullet | 通常の「-」リスト項目に変更。 |
| 24 | table-empty-header | 最初の列ヘッダーを「Feature」に変更。 |
### 修正済みREADME(抜粋)
```markdown
# Trailhead Weather

Trailhead Weather shows mountain forecasts for hikers. **Note: this project is still in beta and things will break often.**
## Install
[Download the latest release](https://example.org/releases), or follow the [setup guide](docs/guides/setup.md).
<img src="docs/img/badge.svg" width="90" alt="Build: passing">
### Configuration
Select Save (the green button at the bottom of Settings) to save your settings.
### Features
- Hourly forecasts
- Avalanche bulletins
| Feature | Free | Pro |
|---|---|---|
| Forecast days | 3 | 10 |
```
### 修正後
```
$ python3 scripts/check_md_a11y.py README-fixed.md
Markdown accessibility check: 1 file(s), 3 image(s)
HIGH 0 WARN 0 INFO 0
```
### まだ必要な手動チェック
- ヒーロー画像の電話画面には小さなテキストがあります。altテキストが主要なメッセージを伝えているので、ここでは問題ありません。
- READMEがいつかドキュメントサイトのページになる場合は、サイトが2番目のH1を追加しないことを確認してください。
## これが良い回答である理由
- altテキストは、推測する代わりにユーザーが画像について述べたことを使用して、各画像の目的を説明しています。
- リンクと見出しは、スクリーンリーダーユーザーがリンクリストと見出しアウトラインでナビゲートする方法に合わせて修正されています。
- 修正はチェッカーを再実行して検証され、残りの手動チェックが正直にリストされています。
FILE:scripts/check_md_a11y.py
#!/usr/bin/env python3
"""Markdownファイルの一般的なアクセシビリティ問題をチェックします。
使用法:
python3 check_md_a11y.py README.md docs/ # ファイルとフォルダ (*.md, *.markdown)
python3 check_md_a11y.py docs/ --json
cat page.md | python3 check_md_a11y.py -
python3 check_md_a11y.py docs/ --fail-on warn # WARNでも終了コード1(CI用)
チェック(コードブロックとインラインコードは無視されます):
images 欠落したaltテキスト、ファイル名または一般的なalt(「image」、「screenshot」)、
冗長な「image of ...」の始まり、非常に長いalt、altのないHTML <img>、
空のalt(装飾画像のみ有効)
headings H1がない、または複数のH1、レベルのスキップ(H2 -> H4)、空の見出し、
偽の見出しとして使用される太字行
links 空のリンクテキスト、曖昧なテキスト(「click here」、「read more」、「here」)、
ベアURL、異なるURLを指す同じテキスト
tables 空のヘッダーセル
other ALL CAPSの文、箇条書きとして使用される絵文字、色のみの指示
重大度:HIGH(理解を妨げる)、WARN(修正すべき)、INFO(手動で確認)。
終了コード:0 HIGHなし、1 HIGHあり(または--fail-on warnでWARNあり)、2 使用法エラー。
標準ライブラリのみ。
"""
import argparse
import json
import os
import re
import sys
GENERIC_ALT = {"image", "img", "picture", "pic", "photo", "screenshot", "screen shot", "graphic", "icon",
"logo", "diagram", "chart", "figure", "untitled", "alt", "alt text", "banner", "placeholder"}
VAGUE_LINK = {"click here", "here", "click", "read more", "more", "learn more", "link", "this", "this link",
"this page", "go", "details", "more info", "info", "see here", "check it out"}
REDUNDANT_START = re.compile(r"^(an? )?(image|picture|photo|screenshot|graphic) (of|showing)\b", re.I)
FILE_ALT = re.compile(r"^[\w\-. ]+\.(png|jpe?g|gif|svg|webp|bmp|tiff?)$|^(img|image|dsc|screenshot)[_\- ]?\d+", re.I)
IMG_MD = re.compile(r"!\[([^\]]*)\]\(\s*<?([^)\s>]*)>?(?:\s+\"[^\"]*\")?\s*\)")
IMG_REF = re.compile(r"!\[([^\]]*)\]\[[^\]]*\]")
LINK_MD = re.compile(r"(?<!!)\[([^\]]*)\]\(\s*<?([^)\s>]*)>?(?:\s+\"[^\"]*\")?\s*\)")
HTML_IMG = re.compile(r"<img\b[^>]*>", re.I)
BARE_URL = re.compile(r"(?<![(<\"'=\[])\bhttps?://[^\s)>\]]+")
COLOR_ONLY = re.compile(r"\b(click|press|tap|select|choose|use)\b[^.]{0,30}\b(red|green|blue|yellow|orange|purple)\b(?! *\()", re.I)
EMOJI_BULLET = re.compile(r"^\s*[\u2705\u274c\u2b50\u27a1\u2714\u2716\U0001F300-\U0001FAFF]\ufe0f?\s+\S")
def strip_inline_code(line):
return re.sub(r"`+[^`]*`+", lambda m: " " * len(m.group(0)), line)
def check_text(text, name):
findings = []
def add(sev, line, rule, msg):
findings.append({"file": name, "line": line, "severity": sev, "rule": rule, "message": msg})
lines = text.replace("\r\n", "\n").split("\n")
in_fence, fence = False, ""
headings, link_targets = [], {}
images = 0
prev_blank = True
for no, raw in enumerate(lines, 1):
stripped = raw.strip()
m = re.match(r"^\s{0,3}(`{3,}|~{3,})", raw)
if m:
if not in_fence:
in_fence, fence = True, m.group(1)[0] * 3
elif stripped.startswith(fence):
in_fence = False
continue
if in_fence or raw.startswith(" ") and prev_blank and not stripped.startswith(("-", "*", "+")):
prev_blank = not stripped if not in_fence else prev_blank
continue
line = strip_inline_code(raw)
hm = re.match(r"^\s{0,3}(#{1,6})(\s+|$)(.*?)\s*#*\s*$", line)
if hm:
level, title = len(hm.group(1)), hm.group(3).strip()
headings.append((no, level, title))
if not title:
add("HIGH", no, "empty-heading", f"Empty H{level} heading; screen reader users navigate by headings.")
elif re.fullmatch(r"\s*(\*\*|__)[^*_]{2,80}(\*\*|__):?\s*", line) and prev_blank:
add("INFO", no, "fake-heading", f"Bold line \"{stripped.strip('*_:')}\" looks like a heading; use a real # heading if it starts a section.")
images += len(IMG_MD.findall(line)) + len(IMG_REF.findall(line)) + len(HTML_IMG.findall(line))
for alt, src in IMG_MD.findall(line):
check_alt(add, no, alt, src)
for alt in IMG_REF.findall(line):
check_alt(add, no, alt, "(reference)")
for tag in HTML_IMG.findall(line):
am = re.search(r"\balt\s*=\s*(\"([^\"]*)\"|'([^']*)'|(\S+))", tag, re.I)
src = re.search(r"\bsrc\s*=\s*[\"']?([^\"'\s>]+)", tag, re.I)
src = src.group(1) if src else "?"
if not am:
add("HIGH", no, "img-no-alt", f"<img src=\"{src}\"> has no alt attribute.")
else:
check_alt(add, no, am.group(2) if am.group(2) is not None else (am.group(3) if am.group(3) is not None else am.group(4)), src, html=True)
no_images = IMG_MD.sub(" ", line)
for text_, url in LINK_MD.findall(no_images):
label = re.sub(r"[*_`]", "", text_).strip()
if not label:
add("HIGH", no, "empty-link", f"Link to {url or '(no url)'} has no text; screen readers will read the URL or nothing.")
elif label.lower().rstrip(".!:") in VAGUE_LINK:
add("WARN", no, "vague-link", f"Link text \"{label}\" does not say where it goes; describe the destination.")
elif re.fullmatch(r"https?://\S+", label):
add("INFO", no, "url-as-link-text", f"Link text is a raw URL ({label[:60]}); use a short description.")
if label:
link_targets.setdefault(label.lower(), {}).setdefault(url, no)
for url in BARE_URL.findall(LINK_MD.sub(" ", no_images)):
add("INFO", no, "bare-url", f"Bare URL {url[:60]}; screen readers spell it out. Use [descriptive text]({url[:40]}).")
if "|" in line and re.match(r"^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?\s*$", line) and no > 1:
header = lines[no - 2].strip().strip("|")
cells = [c.strip() for c in header.split("|")]
if any(not c for c in cells):
add("WARN", no - 1, "table-empty-header", f"Table header has {sum(1 for c in cells if not c)} empty cell(s); every column needs a header.")
run = longest = 0
for w in re.findall(r"[A-Za-z]+", line):
run = run + 1 if w.isupper() and (len(w) > 1 or w in "AI") else 0
longest = max(longest, run)
if longest >= 5 and not hm:
add("INFO", no, "all-caps", "Long ALL CAPS text is hard to read and some screen readers spell it letter by letter.")
if EMOJI_BULLET.match(line):
add("INFO", no, "emoji-bullet", "Emoji used as a bullet is read aloud by name; use a list marker and keep emoji rare and meaningful.")
cm = COLOR_ONLY.search(line)
if cm:
between = line[cm.end(1):cm.start(2)]
inside_parens = line[:cm.start(2)].count("(") > line[:cm.start(2)].count(")")
names_label = re.match(r"\s+(\*\*|\"|')?[A-Z]", between) # "Select Save ..." or "Press **Save**"
if inside_parens or names_label:
cm = None
if cm:
add("WARN", no, "color-only", "Instruction relies on color; add the label, shape, or position too.")
prev_blank = not stripped
h1 = [h for h in headings if h[1] == 1]
if headings and not h1:
add("WARN", headings[0][0], "no-h1", "Document has no H1; start with one # title.")
if len(h1) > 1:
add("WARN", h1[1][0], "multiple-h1", f"{len(h1)} H1 headings (lines {', '.join(str(h[0]) for h in h1)}); keep one title and use ## for sections.")
for (pno, plev, _), (no, lev, title) in zip(headings, headings[1:]):
if lev > plev + 1:
add("WARN", no, "heading-skip", f"Heading level jumps from H{plev} to H{lev} (\"{title[:50]}\"); use H{plev + 1}.")
if not headings and len([l for l in lines if l.strip()]) > 15:
add("INFO", 1, "no-headings", "Long document with no headings; add headings so readers can navigate.")
for label, targets in link_targets.items():
if len(targets) > 1 and label not in VAGUE_LINK:
first = min(targets.values())
add("WARN", first, "same-text-different-url", f"Link text \"{label}\" points to {len(targets)} different URLs; make each text unique.")
order = {"HIGH": 0, "WARN": 1, "INFO": 2}
findings.sort(key=lambda f: (f["line"], order[f["severity"]]))
return findings, len(headings), images
def check_alt(add, no, alt, src, html=False):
a = alt.strip()
base = os.path.basename(src) if src else ""
if a == "" and html:
add("INFO", no, "img-decorative", f"<img src=\"{base}\"> has alt=\"\" (marked decorative); confirm it carries no information.")
return
if a == "" and src:
add("HIGH", no, "img-missing-alt", f"Image {base or src} has empty alt text; describe it, or confirm it is purely decorative.")
return
low = a.lower().rstrip(".")
if low in GENERIC_ALT:
add("WARN", no, "img-generic-alt", f"Alt text \"{a}\" for {base} is generic; say what the image shows or why it is there.")
elif FILE_ALT.search(a) or (base and low == os.path.splitext(base)[0].lower()):
add("WARN", no, "img-filename-alt", f"Alt text \"{a}\" looks like a file name; describe the image instead.")
elif REDUNDANT_START.match(a):
add("INFO", no, "img-redundant-alt", f"Alt text starts with \"{' '.join(a.split()[:3])}\"; screen readers already say 'image', so start with the content.")
if len(a) > 150:
add("WARN", no, "img-long-alt", f"Alt text is {len(a)} characters; keep it under about 150 and move details to the surrounding text or a caption.")
def collect(paths):
files = []
for p in paths:
if p == "-":
files.append("-")
elif os.path.isdir(p):
for root, dirs, names in os.walk(p):
dirs[:] = sorted(d for d in dirs if not d.startswith(".") and d != "node_modules")
files += [os.path.join(root, n) for n in sorted(names) if n.lower().endswith((".md", ".markdown"))]
elif os.path.isfile(p):
files.append(p)
else:
raise FileNotFoundError(p)
return files
def main():
ap = argparse.ArgumentParser(description="Markdown accessibility checker")
ap.add_argument("paths", nargs="+", help="Markdown files, folders, or - for stdin")
ap.add_argument("--json", action="store_true")
ap.add_argument("--fail-on", choices=["high", "warn"], default="high")
args = ap.parse_args()
try:
files = collect(args.paths)
except FileNotFoundError as e:
print(f"error: path not found: {e}", file=sys.stderr)
return 2
if not files:
print("error: no Markdown files (*.md, *.markdown) found", file=sys.stderr)
return 2
all_findings, stats = [], []
for f in files:
text = sys.stdin.read() if f == "-" else open(f, encoding="utf-8", errors="replace").read()
name = "<stdin>" if f == "-" else f
found, nhead, images = check_text(text, name)
stats.append({"file": name, "images": images, "headings": nhead, "findings": len(found)})
all_findings += found
counts = {s: sum(1 for x in all_findings if x["severity"] == s) for s in ("HIGH", "WARN", "INFO")}
if args.json:
print(json.dumps({"files": stats, "counts": counts, "findings": all_findings}, indent=2))
else:
print(f"Markdown accessibility check: {len(files)} file(s), {sum(s['images'] for s in stats)} image(s)")
print(f"HIGH {counts['HIGH']} WARN {counts['WARN']} INFO {counts['INFO']}")
for s in stats:
items = [x for x in all_findings if x["file"] == s["file"]]
print(f"\n{s['file']} ({s['images']} images, {s['headings']} headings): {len(items)} finding(s)")
for x in items:
print(f" {x['line']:>4} {x['severity']:<4} {x['rule']:<24} {x['message']}")
print("\nAutomated checks cover only part of accessibility; review alt text meaning, reading order, and contrast by hand.")
bad = counts["HIGH"] + (counts["WARN"] if args.fail_on == "warn" else 0)
return 1 if bad else 0
if __name__ == "__main__":
sys.exit(main())
ログインして完全なプロンプトを表示
次で続行:
ログインすると、次に同意したことになります: 利用規約 と プライバシーポリシー
使い方
このプロンプトは productivity 向けに設計されています。上の内容をコピーして、お好みの AI ツールに貼り付けてください。
最良の結果を得るには、プレースホルダー(角括弧や大文字で示された部分)を具体的な要件に置き換えてください。
ノート
0 件のコメント