HTMLとCSSのコード添削を自動でするプログラムを作ってみた

Web制作でHTMLを修正した際、不要なCSSが残ったり、逆にクラスを書き忘れたりすることはありませんか?この記事では、Rubyを使ってHTMLファイルとCSSファイルを照合し、クラスの不整合を自動で抽出するスクリプトを紹介します。(スクリプト:一般的にはプログラムには準備(コンパイル)が必要だけど、スクリプトは作ってすぐに動かせるプログラムなので手軽)

チェック項目手動(目視)このスクリプト
正確性見落としが発生しやすい機械的に100%抽出
作業時間数十分〜数秒
対応範囲1ファイルが限界複数クラスも一括判定

前提条件(実行環境)

このスクリプトを動かすには、以下の環境とライブラリが必要です。

  • 開発言語 : Ruby 3.x 以上
  • 対象ファイル : .html ファイルおよび .css ファイル(同じディレクトリ内、またはサブディレクトリ内)
  • 必要なライブラリ(Gem):
  • nokogiri : HTMLの構造を解析し、クラス名を取り出すために使用します。
  • css_parser : CSSファイルを解析し、定義されているスタイルを読み取るために使用します。

↓以下は、具体的な手順です。

PCにRubyを入れてみる

まず自分のPCにRubyが入っているか、確認

コマンドプロンプトを立ち上げて

ruby -v

と入れてみる。

「Rubyは…認識されていません」ってことで、入ってない。

Rubyのサイトに行って、ダウンロード → インストール

RubyInstaller for Windows
The easy way to install Ruby on Windows This is a self-contained Windows-based installer that includes the Ruby language...

太字になっているのがオススメだと思うんだ。

Rubyがインストールできたら

この画面の一番下にある Which components shall be installed? If unsure press ENTER [1,3] の横に、半角数字の 3 を入力して、キーボード「Enter」キー。

たーくさん、文字が出てきます。succeeded(成功)が出ればOKとのこと。

succeeded(成功)が確認できたら、この画面は閉じる。

Gemのインストール

またまたコマンドプロンプトを立ち上げる。

gem install nokogiri 

と入れ、エンターキー

▼「gem install nokogiri 」というコードの意味

gem : Rubyのライブラリ(gem)を管理するためのコマンド(インストール、アンインストールなど)。
install : 指定したライブラリ(gem)をインストールする命令。
nokogiri : インストールしたいライブラリの名前(HTML/XMLパーサー)。

gem install nokogiri は、「RubyでWebスクレイピング(Webサイトから情報を抽出・取得する)ための定番ライブラリであるNokogiriを、Rubyのパッケージ管理システム(gem)を使ってインストールする」という意味らしい。

1 gem installed

と表示されれば成功

とりあえずこの画面はこれで閉じました(続けてできる気もするけど、ビビリなので)

CSSのチェック用のライブラリをインストール

紆余曲折の末、CSSもチェックしたいので、CSSチェック用のライブラリ「 css_parser 」を入れることに。またまたコマンドプロンプトを立ち上げる。

gem install css_parser

と入れエンターキー。

1 gem installed

と表示されれば成功。

フォルダをデスクトップに作成

デスクトップに「 コード添削 」フォルダを作成。その中に「 提出物 」フォルダを作成。
Rubyのプログラムファイル(後述)も「 コード添削 」フォルダに入れる。
※「 提出物 」フォルダの中に、生徒さん達の提出フォルダ(サイトのデータ)を入れる。

RubyでHTMLとCSSのクラス差分をチェックするコードを作成

Rubyのコード(プログラムの命令文)は以下の通り。ファイル名は「 check.rb 」にする。
・・・まぁ、何人も見たら、改良しなければならないところがまた出てくるかもしれないけど、今の段階ではこんな感じ。
HTMLのバージョンはHTML5に固定。基本的な文法ができてるかチェック。リンクの不備、CSSは基本的な文法が合ってるかとクラスとIDの重複をチェック。使われていないクラスを検出。

require 'nokogiri'
require 'fileutils'
require 'pathname'

SUBMISSION_DIR = '提出物'
REPORT_DIR = '添削レポート結果'

FileUtils.mkdir_p(REPORT_DIR)

Dir.glob("#{SUBMISSION_DIR}/*").each do |student_path|
  next unless File.directory?(student_path)
  student_name = File.basename(student_path)
  puts "添削中: #{student_name} ..."

  report = ["========================================", "  生徒名: #{student_name} 様 添削レポート", "========================================\n"]
  
  all_files = Dir.glob("#{student_path}/**/*", File::FNM_DOTMATCH).select { |f| File.file?(f) }

  # --- 1. 事前準備:全HTMLから使用中のClass/IDをすべて収集 ---
  all_htmls = Dir.glob("#{student_path}/**/*.html")
  used_in_html_classes = []
  used_in_html_ids = []

  all_htmls.each do |html_path|
    html_content = File.read(html_path, encoding: "UTF-8") rescue ""
    doc = Nokogiri::HTML5.parse(html_content)
    
    # Classの抽出
    doc.css('[class]').each do |el|
      used_in_html_classes += el['class'].split(/\s+/)
    end
    # IDの抽出
    doc.css('[id]').each do |el|
      used_in_html_ids << el['id'].delete('#')
    end
  end
  used_in_html_classes.uniq!
  used_in_html_ids.uniq!

  # --- 2. 各HTMLファイルの解析(パス・文法・うっかりミス) ---
  all_htmls.each do |html_path|
    rel_html_path = Pathname.new(html_path).relative_path_from(Pathname.new(student_path)).to_s
    report << "--- HTMLチェック: #{rel_html_path} ---"
    
    current_dir = File.dirname(html_path)
    current_folder_name = File.basename(current_dir)
    html_lines = File.readlines(html_path, encoding: "UTF-8") rescue []
    doc = Nokogiri::HTML5.parse(html_lines.join)
    
    if doc.errors.any?
      report << "❌ HTML文法にエラーがあります。"
    else
      report << "✅ HTML文法はエラー無です。"
      
      # ID名に「#」を入れちゃうミス
      doc.css('[id]').each do |el|
        id_val = el['id']
        if id_val && id_val.start_with?('#')
          line_no = html_lines.find_index { |l| l.include?("id=\"#{id_val}\"") }
          report << "⚠️ #{line_no ? line_no + 1 : '?'}行目: ID名に「#」は不要です。id=\"#{id_val.delete('#')}\" に直しましょう。"
        end
      end

      # リンクチェック(スペース・冗長パス対応)
      doc.css('img, a, link').each do |el|
        attr_name = (el.name == 'img') ? 'src' : 'href'
        path = el[attr_name]
        next if path.nil? || path.strip.empty? || path.start_with?('http', 'mailto:', 'tel:')

        line_no = html_lines.find_index { |l| l.include?(path) }
        line_prefix = line_no ? "#{line_no + 1}行目: " : ""

        # スペース異常の検知
        if path.include?(" #") || path.end_with?(" ")
          report << "⚠️ #{line_prefix}パスに不要なスペースがあります。「#{path.gsub(' ', '␣')}」を詰めましょう。"
        end

        # パスのクリーンアップと実在確認
        clean_path = path.strip.gsub(" #", "#")
        target_file_part = clean_path.split('#').first || ""
        check_filename = target_file_part.empty? ? File.basename(html_path) : target_file_part

        target_full_path = File.expand_path(check_filename, current_dir)
        actual_file = all_files.find { |f| File.expand_path(f) == target_full_path }

        if actual_file.nil?
          report << "❌ #{line_prefix}リンク切れ「#{path}」"
        else
          actual_rel = Pathname.new(actual_file).relative_path_from(Pathname.new(current_dir)).to_s.gsub('\\', '/')
          # 冗長パス
          if clean_path.include?("../#{current_folder_name}/")
            report << "⚠️ #{line_prefix}パスが冗長です。「#{actual_rel}」と短く書けます。"
          elsif check_filename.gsub('\\', '/') != actual_rel && !path.include?(" #")
            report << "⚠️ #{line_prefix}大文字・小文字不一致(指定:#{check_filename} ⇔ 実際:#{actual_rel})"
          end
        end
      end
    end
    report << ""
  end

  # --- 3. CSS解析(重複チェック + 未使用セレクタ検出) ---
  Dir.glob("#{student_path}/**/*.css").each do |path|
    rel_css_path = Pathname.new(path).relative_path_from(Pathname.new(student_path)).to_s
    report << "--- CSSチェック: #{rel_css_path} ---"
    css_lines = File.readlines(path, encoding: "UTF-8") rescue []
    
    raw_selectors = []
    unused_items = []

    css_lines.each_with_index do |line, idx|
      if line.include?('{')
        selector = line.split('{').first.strip.gsub(/\s+/, " ")
        raw_selectors << { name: selector, line: idx + 1 }

        # HTMLで使われていないクラス・IDを抽出
        # クラス (.name)
        selector.scan(/\.([a-zA-Z0-9\-_]+)/).flatten.each do |cls|
          unused_items << ".#{cls} (#{idx + 1}行目)" unless used_in_html_classes.include?(cls)
        end
        # ID (#name)
        selector.scan(/#([a-zA-Z0-9\-_]+)/).flatten.each do |id_name|
          next if id_name == "top" # ページトップ用IDは除外
          unused_items << "##{id_name} (#{idx + 1}行目)" unless used_in_html_ids.include?(id_name)
        end
      end
    end

    # 重複チェック
    selector_names = raw_selectors.map { |s| s[:name] }
    duplicates = selector_names.select { |s| selector_names.count(s) > 1 }.uniq
    if !duplicates.empty?
      report << "⚠️ 重複アドバイス:"
      duplicates.each do |target|
        lines = raw_selectors.select { |s| s[:name] == target }.map { |s| s[:line] }
        report << "  ・「#{target}」が #{lines.join(', ')}行目にあります。"
      end
    end

    # 未使用アドバイス
    if !unused_items.empty?
      report << "❓ 未使用アドバイス(HTML側に記述が見当たりません):"
      unused_items.uniq.each { |item| report << "  ・#{item}" }
    end

    report << "✅ チェック完了" if duplicates.empty? && unused_items.empty?
    report << ""
  end

  report << "========================================\n"
  File.write("#{REPORT_DIR}/#{student_name}_レポート.txt", report.join("\n"), mode: "w:UTF-8")
end
puts "\n✨ 未使用CSS検出機能を含む【完全版】の添削が完了しました!"

HTMLとCSSをチェックするスクリプトの実行手順と結果の見方

HTMLとCSSをチェックするスクリプトの実行手順

スクリプト(プログラム)を実行するときは、コマンドプロンプトから。
↓私の「 コード添削 」フォルダのファイルパスを入れて、そちらへ移動。

cd /d "C:\Users\username\デスクトップ\コード添削"

※コードは例。パスの中に日本語が含まれる場合、全体を ” “(ダブルクォーテーション)で囲む。

↓先ほど作った「 check.rb 」を実行する

ruby check.rb

HTMLとCSSをチェックするスクリプトの結果の見方

HTMLとCSS、チェックの結果が表示されたら、実際のファイルを開いて、該当箇所を確認。
CSSは、同じファイル内に重複箇所があれば出してきますが、メディアクエリ内のものも重複で出してきます。なのでPC用とスマホ用のCSSを書いていれば重複に表示されます。
それから、CSSにはあるけど、HTMLにはそのクラス名やID名がない場合も表示されます。

おまけ:Geminiにプログラムの紹介文を考えてもらった

余談:このプログラムができた背景

冬休みの宿題
私はサーバーにアップして、まとめサイトに掲載するだけで疲れたので、各自が自分でアップされた自分のサイトを見て、リンク切れ等に自分で気が付いて自分で直してよ~、と思っていたのだが・・・
相方のコーディングの先生がひとり、ひとりにフィードバック返してくれて、感動😭
(ほんと、相方は講師に向いてるんよね。一人一人に向き合ってくれてるし、立派)


・・・と、言いつつ、そもそも各自が自分でバリデーションしたらよいことではないか?
↓習っているはずなんだがな🫠(教科書にも書いているし、相方も教えていたはず…)
https://validator.w3.org/ (W3C マークアップ検証サービス)
https://css-validator.org/validator.ja.html (W3C CSS Validation Service)

各生徒のコードに不備がないか、それを時間外にチェックする重労働よ・・・🫠と感じたので、自動で添削するプログラムをつくってみた。とはいっても、Geminiに教えてもらったんだけど。
結論から言うと、無料で動かしたかったので、自分のPCにRubyをインストールしたの。
これはあくまでも相方の姿を見て、相方の手間を省くために考えたものなんだけど、公開することで「自分でやってみよう」と思う人はやってほしいなぁ~。(と、自分のクラスの人が見ていて、自分でもやってみることを期待w)

・・・相方の先生には「こんなの作ったよー」って伝えたんだけど、このプログラムを作った本人であるGeminiに、なんて言ったらいいと思う?と聞いたところ、紹介文を考えてくれたので、掲載しておくよ。

プログラム名:『自走力を育てる!HTML/CSS添削アシスタント』

このプログラムは、講師の目視チェックを大幅に効率化し、生徒が自分では気づきにくい「おしいミス」を自動でリストアップするツールです。

1. 初学者の「ハマりポイント」を逃さず検出

初心者が数時間悩んでしまうような、小さな「書式ミス」を即座に見つけます。

  • 「パスの半角スペース」検知
    • href="index.html #news" のように、ファイル名と # の間にスペースがあると、一部の環境でリンクが機能しません。これを「スペースを詰めましょう」と具体的に指摘します。
  • 「ID名に#」検知
    • id="#top" と書いてしまうミス(正解は id="top")は非常に多いですが、これを見逃さず修正を促します。
  • 「大文字・小文字の不一致」検知
    • 自分のパソコン(Windows等)では動くのに、提出サーバーでは動かない原因になるファイル名のスペルミスを特定します。

2. コードの「整理・美しさ」へのアドバイス

単に「動く」だけでなく、プロとして必要な「メンテナンス性の高いコード」を教えるきっかけを作ります。

  • 「未使用CSS」の自動特定(リファクタリング機能)
    • ここがポイント!:「CSSに定義はあるが、HTML側で一度も使われていないクラスやID」をリストアップします。
    • 過去の課題のCSSを流用して不要なコードが残っている場合や、HTMLとCSSで名前が食い違っている(スペルミス)場合に非常に有効です。
  • 「冗長なパス」の最適化
    • 同じフォルダ内のファイルへリンクするのに、わざわざ ../ で外に出てから自分自身のフォルダを指定するような、不自然な書き方を検知します。

3. 添削業務のスピードアップと標準化

  • レポート自動生成: 実行すると生徒ごとの「添削レポート.txt」が生成されます。そのままフィードバックとして返却可能です。
  • 多階層プロジェクト対応: 複数のフォルダに分かれた複雑な課題も、一括でスキャンして index.html の有無からチェックします。

先生方へ伝える際の「おすすめの言葉」

「このツールを使うと、『スペルミス探し』という単純作業から解放されます。 浮いた時間で、生徒さんのデザイン構成やアクセシビリティについてなど、人間にしかできないより高度なアドバイスに時間を使えるようになりますよ!」