応用①:フォーム仕様書の Markdown 自動生成

抽出した InputEntity リストを Markdown のテーブルとして出力すれば、 そのまま仕様書・設計書のドラフトとして使える。

Python — generate_spec.py
from extractor import extract_inputs
from extractor.models import InputEntity
from pathlib import Path


def generate_markdown_spec(entities: list[InputEntity], title: str = "フォーム仕様書") -> str:
    """InputEntity リストを Markdown テーブルに変換する"""
    lines = [f"# {title}", "", "| # | 表示名 | type | name | 必須 | 最大文字 | 選択肢 |",
             "|---|--------|------|------|------|----------|--------|"]

    for i, e in enumerate(entities, start=1):
        label = e.label or "(なし)"
        options = " / ".join(e.options[:5]) if e.options else "-"
        if len(e.options) > 5:
            options += " ..."
        lines.append(
            f"| {i} | {label} | `{e.type}` | `{e.name or '-'}` "
            f"| {'✅' if e.required else '-'} "
            f"| {e.maxlength or '-'} "
            f"| {options} |"
        )

    return "\n".join(lines)


if __name__ == "__main__":
    url = "https://httpbin.org/forms/post"
    entities = extract_inputs(url)
    md = generate_markdown_spec(entities, title=f"フォーム仕様書 — {url}")
    out = Path("output/form_spec.md")
    out.parent.mkdir(exist_ok=True)
    out.write_text(md, encoding="utf-8")
    print(md)
出力 Markdown(抜粋)
# フォーム仕様書 — https://httpbin.org/forms/post

| # | 表示名 | type | name | 必須 | 最大文字 | 選択肢 |
|---|--------|------|------|------|----------|--------|
| 1 | Customer name | `text` | `custname` | - | - | - |
| 2 | Telephone | `tel` | `custtel` | - | - | - |
| 3 | E-mail address | `email` | `custemail` | - | - | - |
| 4 | Pizza Size | `select` | `size` | - | - | Small / Medium / Large |
| 5 | Any comments? | `textarea` | `comments` | - | - | - |

応用②:テストケースインプット生成

required / maxlength / type 情報からテスト設計でよく使う入力値候補を自動生成できる。

Python — generate_testcases.py
from extractor.models import InputEntity


def generate_test_inputs(entity: InputEntity) -> list[dict]:
    """
    1 つの InputEntity から境界値・等価クラスのテスト入力値を生成する。

    Returns:
        {"case": "説明", "value": 入力値, "expected": "期待結果"} のリスト
    """
    cases = []

    # 必須チェック
    if entity.required:
        cases.append({"case": "必須: 未入力",          "value": "",   "expected": "バリデーションエラー"})
        cases.append({"case": "必須: 入力あり(正常)", "value": "テスト値", "expected": "OK"})

    # 最大文字数チェック
    if entity.maxlength:
        max_len = int(entity.maxlength)
        cases.append({"case": f"最大文字: 上限ちょうど ({max_len}文字)",
                       "value": "A" * max_len,         "expected": "OK"})
        cases.append({"case": f"最大文字: 上限超過 ({max_len + 1}文字)",
                       "value": "A" * (max_len + 1),   "expected": "バリデーションエラー"})

    # type 別テスト値
    if entity.type == "email":
        cases.append({"case": "email: @なし",  "value": "invalid",         "expected": "バリデーションエラー"})
        cases.append({"case": "email: 正常形式", "value": "test@example.com", "expected": "OK"})

    elif entity.type == "number":
        if entity.min:
            cases.append({"case": f"number: 下限未満 ({entity.min} - 1)",
                           "value": str(int(entity.min) - 1), "expected": "バリデーションエラー"})
        if entity.max:
            cases.append({"case": f"number: 上限超過 ({entity.max} + 1)",
                           "value": str(int(entity.max) + 1), "expected": "バリデーションエラー"})

    elif entity.type in {"select"}:
        for opt in entity.options:
            cases.append({"case": f"select: 正常値 '{opt}'", "value": opt, "expected": "OK"})

    return cases


# 使用例
from extractor import extract_inputs
entities = extract_inputs("https://httpbin.org/forms/post")
for e in entities:
    test_cases = generate_test_inputs(e)
    if test_cases:
        print(f"\n--- {e.label or e.name} ({e.type}) ---")
        for tc in test_cases:
            print(f"  [{tc['case']}]  value={tc['value']!r:20}  → {tc['expected']}")

発展:DB カラム候補の推定

InputEntity の属性から DB カラムの型・制約を推定するヒューリスティックを実装できる。

Python — DB カラム候補推定(ヒューリスティック)
TYPE_MAP = {
    "text":     "VARCHAR",
    "email":    "VARCHAR",
    "tel":      "VARCHAR",
    "password": "VARCHAR",
    "number":   "INTEGER",
    "date":     "DATE",
    "textarea": "TEXT",
    "checkbox": "BOOLEAN",
    "radio":    "VARCHAR",
    "select":   "VARCHAR",
    "hidden":   "VARCHAR",
}

def to_column_def(e: InputEntity) -> dict:
    sql_type = TYPE_MAP.get(e.type, "VARCHAR")
    if sql_type == "VARCHAR" and e.maxlength:
        sql_type = f"VARCHAR({e.maxlength})"
    return {
        "column_name": e.name or "(不明)",
        "sql_type":    sql_type,
        "not_null":    e.required,
        "comment":     e.label or e.placeholder or "",
    }

📌 これはあくまで「出発点」

DB 設計はドメイン知識が必要な作業だ。自動推定した結果を無批判に採用するのではなく、設計者がレビューして調整する前提で使うこと。

次の章では…

PART 09 では SSL エラー・文字化け・JS 未描画など、実際の現場でよく遭遇するエラーとその対処法をまとめます。

→ PART 09 — トラブルシューティングへ