【完全ガイド】Ruby文字列操作の基礎から応用まで15の実践テクニック

Rubyの文字列操作が重要な理由

多くのRubyプロジェクトで必要不可欠なスキル

Rubyにおける文字列操作は、ほぼすべてのプロジェクトで必要となる基礎的かつ重要なスキルです。その理由として、以下のような実務での活用シーンが挙げられます:

  • データ処理と変換
  • CSVやJSONなどの構造化データの解析
  • APIレスポンスの加工
  • ユーザー入力の検証と整形
  • テキスト生成
  • レポートやログの出力
  • HTMLテンプレートの生成
  • 動的なSQL文の構築
  • ファイル操作
  • ログファイルの解析
  • 設定ファイルの読み込みと解析
  • テキストファイルの加工

例えば、以下のようなコードは実務でよく目にする文字列処理の例です:

# ユーザー入力の検証と整形
def sanitize_user_input(input)
  input.strip.downcase.gsub(/[^a-z0-9\s]/, '')
end

# CSVデータの処理
def process_csv_line(line)
  fields = line.split(',').map(&:strip)
  {
    name: fields[0],
    email: fields[1].downcase,
    age: fields[2].to_i
  }
end

効率的な文字列処理がパフォーマンスを左右する

文字列操作は頻繁に行われる処理であるため、その実装方法はアプリケーション全体のパフォーマンスに大きな影響を与えます:

  1. メモリ使用量への影響
  • 文字列の不適切な処理は、メモリの過剰消費を引き起こす可能性があります
  • 特に大量のデータを扱う場合、効率的な文字列処理が重要になります
# メモリ効率の悪い実装
def inefficient_concat(strings)
  result = ""
  strings.each { |s| result += s }  # 毎回新しい文字列オブジェクトを生成
  result
end

# メモリ効率の良い実装
def efficient_concat(strings)
  strings.join  # 一度の操作で文字列を結合
end
  1. 処理速度への影響
  • 適切な文字列操作メソッドの選択が処理速度を大きく左右します
  • 特に正規表現やパターンマッチングの使用方法は重要です
# パフォーマンスの悪い実装
def slow_check(text, words)
  words.all? { |word| text.include?(word) }  # 毎回文字列全体をスキャン
end

# パフォーマンスの良い実装
def fast_check(text, words)
  pattern = /#{words.join('|')}/i  # 一度の正規表現で複数のワードをチェック
  text.match?(pattern)
end

文字列操作の重要性は、単なる機能実装の観点だけでなく、アプリケーションの品質全体に関わる重要な要素となっています。効率的な文字列処理の実装は、以下のような利点をもたらします:

  • アプリケーションの応答性向上
  • サーバーリソースの効率的な利用
  • 保守性の高いコードの実現
  • スケーラビリティの確保

このため、Rubyエンジニアには文字列操作の基礎から応用まで、体系的な理解と実装スキルが求められています。

文字列の基本概念と作成方法

シングルクォートとダブルクォートの使い分け

Rubyでは、文字列を作成する際に主にシングルクォート(’)とダブルクォート(”)の2種類の方法があります。それぞれには特徴があり、用途に応じて適切に使い分けることが重要です。

# シングルクォート
str1 = 'Hello, Ruby'   # 基本的な文字列
str2 = 'It\'s Ruby'    # エスケープが必要
str3 = 'Line 1\nLine 2' # \n はそのまま文字として扱われる

# ダブルクォート
str4 = "Hello, Ruby"   # 基本的な文字列
str5 = "It's Ruby"     # エスケープ不要
str6 = "Line 1\nLine 2" # \n は改行として解釈される
name = "Alice"
str7 = "Hello, #{name}" # 式展開が可能

使い分けのポイント:

特徴シングルクォートダブルクォート
式展開不可可能
エスケープシーケンス最小限全て対応
パフォーマンスやや高速やや低速
使用推奨シーン単純な文字列
式展開不要な場合
式展開が必要な場合
エスケープシーケンスを使用する場合

ヒアドキュメントを使った複数行文字列の作成

複数行にわたる文字列を扱う場合、ヒアドキュメント(heredoc)を使用すると可読性の高いコードを書くことができます。

# 基本的なヒアドキュメント
message = <<TEXT
これは複数行の
テキストメッセージです。
  インデントも保持されます。
TEXT

# インデント付きヒアドキュメント
def send_email
  body = <<~EMAIL
    親愛なるユーザー様

    ご利用ありがとうございます。
    このメールは自動送信されています。

    敬具
    システム管理者
  EMAIL
  # 処理の続き...
end

# 式展開とエスケープを制御
sql = <<-'SQL'
  SELECT *
  FROM users
  WHERE name = '#{name}' -- この#{name}は式展開されない
SQL

html = <<~"HTML"
  <div>
    Hello, #{user.name}! -- この#{user.name}は式展開される
  </div>
HTML

文字列のエンコーディング設定と確認方法

Rubyでは文字列のエンコーディングを適切に扱うことが、特に日本語を含むマルチバイト文字を処理する際に重要です。

# ファイルエンコーディングの指定
# coding: utf-8

# 文字列のエンコーディングを確認
str = "こんにちは"
puts str.encoding  #=> #<Encoding:UTF-8>

# エンコーディングの変換
utf8_str = "Ruby"
sjis_str = utf8_str.encode("Shift_JIS")
puts sjis_str.encoding  #=> #<Encoding:Shift_JIS>

# エンコーディング指定で文字列作成
str_utf8 = "日本語".force_encoding("UTF-8")
str_sjis = "日本語".force_encoding("Shift_JIS")

# エンコーディングエラーの対処
begin
  invalid_str = "こんにちは".encode("ISO-8859-1")
rescue Encoding::UndefinedConversionError => e
  puts "変換できない文字が含まれています: #{e.message}"
end

# 文字列結合時のエンコーディング自動変換
utf8_str = "Hello"
sjis_str = "こんにちは".encode("Shift_JIS")
combined = utf8_str + sjis_str  # SJISがUTF-8に自動変換される

エンコーディング処理の重要なポイント:

  1. デフォルトエンコーディング
  • Ruby 2.0以降はUTF-8がデフォルト
  • ソースコードのエンコーディングはマジックコメントで指定可能
  1. エンコーディング変換のベストプラクティス
  • 入力時に適切なエンコーディングに変換
  • アプリケーション内部ではUTF-8で統一
  • 出力時に必要なエンコーディングに変換
  1. エラー処理
  • force_encodingencodeの使い分けを理解
  • 適切な例外処理の実装
  • 変換できない文字の代替処理の検討

このように、Rubyの文字列は様々な方法で作成・操作できますが、用途に応じて適切な方法を選択することが、可読性の高い効率的なコードの作成につながります。

基本的な文字列操作テクニック

文字列の結合と分割を効率的に行う

文字列の結合と分割は最も頻繁に行われる操作の一つです。Rubyには複数の方法が用意されており、状況に応じて最適な方法を選択することが重要です。

文字列の結合

# 1. + 演算子による結合
name = "Ruby" + " " + "Programming"  # => "Ruby Programming"

# 2. << 演算子による結合(破壊的メソッド)
message = "Hello"
message << " " << "World"  # => "Hello World"

# 3. concat メソッドによる結合(破壊的メソッド)
greeting = "Good"
greeting.concat(" ").concat("Morning")  # => "Good Morning"

# 4. join メソッドによる配列要素の結合
words = ["Ruby", "is", "fun"]
words.join(" ")  # => "Ruby is fun"

# 5. Array#* による繰り返し結合
"Ruby" * 3  # => "RubyRubyRuby"

# パフォーマンス比較例
require 'benchmark'

strings = Array.new(10000) { "string" }

Benchmark.bm do |x|
  x.report("+:") { strings.reduce { |result, str| result + str } }
  x.report("<<:") { strings.reduce { |result, str| result << str } }
  x.report("join:") { strings.join }
end

文字列の分割

# 1. split メソッドによる分割
text = "Ruby,Python,JavaScript"
languages = text.split(",")  # => ["Ruby", "Python", "JavaScript"]

# 2. 正規表現による複雑な分割
text = "Ruby;Python,JavaScript|PHP"
languages = text.split(/[;,|]/)  # => ["Ruby", "Python", "JavaScript", "PHP"]

# 3. 文字数による分割
text = "Ruby programming"
chunks = text.scan(/.{1,5}/)  # => ["Ruby ", "progr", "ammin", "g"]

# 4. 行による分割
text = "Line 1\nLine 2\nLine 3"
lines = text.lines  # => ["Line 1\n", "Line 2\n", "Line 3"]

部分文字列の抽出と置換を使いこなす

文字列から特定の部分を抽出したり、置換したりする操作も頻繁に必要となります。

# 1. 部分文字列の抽出
text = "Ruby Programming"
text[0, 4]      # => "Ruby"  # インデックスと長さを指定
text[5..-1]     # => "Programming"  # 範囲を指定
text[-11..-1]   # => "Programming"  # 末尾からの範囲指定

# 2. 正規表現による抽出
text = "Email: example@ruby.org"
text[/\w+@\w+\.\w+/]  # => "example@ruby.org"

# 3. 文字列置換
text = "I like Python"
text.sub("Python", "Ruby")    # => "I like Ruby"  # 最初の一致のみ置換
text.gsub("Python", "Ruby")   # => "I like Ruby"  # すべての一致を置換

# 4. 複数のパターンを一括置換
text = "I like Python and Python is cool"
replacements = {"Python" => "Ruby", "cool" => "awesome"}
text.gsub(/Python|cool/, replacements)  # => "I like Ruby and Ruby is awesome"

# 5. ブロックを使用した動的な置換
text = "price: 100, tax: 10"
text.gsub(/\d+/) { |match| match.to_i * 2 }  # => "price: 200, tax: 20"

大文字小文字の変換とケース処理

文字列の大文字小文字を変換する操作は、特にユーザー入力の正規化やフォーマット統一に重要です。

# 1. 基本的な大文字小文字変換
text = "Ruby Programming"
text.upcase     # => "RUBY PROGRAMMING"
text.downcase   # => "ruby programming"
text.swapcase   # => "rUBY pROGRAMMING"
text.capitalize # => "Ruby programming"

# 2. 破壊的メソッドバージョン
text = "Ruby Programming"
text.upcase!    # 文字列自体を変更
text.downcase!  # 文字列自体を変更

# 3. タイトルケース変換(独自実装)
def titleize(text)
  text.split(/\s+/).map(&:capitalize).join(" ")
end

text = "ruby on rails programming"
titleize(text)  # => "Ruby On Rails Programming"

# 4. 特殊なケース変換
class String
  def to_snake_case
    self.gsub(/::/, '/')
        .gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
        .gsub(/([a-z\d])([A-Z])/,'\1_\2')
        .tr("-", "_")
        .downcase
  end

  def to_camel_case
    self.split('_').map(&:capitalize).join
  end
end

"ruby_programming".to_camel_case  # => "RubyProgramming"
"RubyProgramming".to_snake_case   # => "ruby_programming"

これらの基本的な文字列操作を理解し、適切に使用することで、より効率的で保守性の高いコードを書くことができます。特に以下の点に注意して使用しましょう:

  1. パフォーマンスの考慮
  • 大量の文字列結合にはjoinを使用
  • 破壊的メソッドと非破壊的メソッドの使い分け
  • 正規表現の過度な使用を避ける
  1. メモリ使用の最適化
  • 大きな文字列を扱う際は破壊的メソッドを検討
  • 不要な中間文字列の生成を避ける
  1. 可読性の維持
  • 複雑な正規表現はコメントで説明を追加
  • 意図が明確になる命名を心がける
  • 汎用的な処理は独自メソッドとして切り出す

正規表現を使った高度な文字列処理

パターンマッチングの基本と応用

Rubyの正規表現は、文字列のパターンマッチングにおいて強力なツールを提供します。基本的な使い方から実践的な応用まで、段階的に見ていきましょう。

基本的なパターンマッチング

# 1. 正規表現オブジェクトの作成
pattern1 = /ruby/i          # 大文字小文字を区別しない
pattern2 = %r{ruby}i        # 別の記法
pattern3 = Regexp.new("ruby", Regexp::IGNORECASE)

# 2. マッチングの基本操作
text = "I love Ruby programming"

# マッチの確認
text.match?(/ruby/i)    # => true (高速、Ruby 2.4以降)
text =~ /ruby/i         # => 7 (マッチした位置)
text.match(/ruby/i)     # => #<MatchData "Ruby">

# 否定マッチ
text !~ /python/i       # => true

# 3. よく使用するパターン例
email = "user@example.com"
phone = "090-1234-5678"
date = "2024-03-15"

email =~ /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i  # メールアドレス
phone =~ /\A\d{2,4}-\d{2,4}-\d{4}\z/           # 電話番号
date =~ /\A\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])\z/  # 日付

高度なパターンマッチング

# 1. 先読み・後読み
text = "price: $100, cost: $50"

# 肯定先読み
text.scan(/\$\d+(?=,)/)      # => ["$100"]  # カンマの前の金額

# 否定先読み
text.scan(/\$\d+(?!,)/)      # => ["$50"]   # カンマが続かない金額

# 2. 条件分岐パターン
def validate_password(password)
  pattern = /\A
    (?=.*[A-Z])           # 少なくとも1つの大文字
    (?=.*[a-z])           # 少なくとも1つの小文字
    (?=.*\d)              # 少なくとも1つの数字
    (?=.*[!@#$%^&*])     # 少なくとも1つの特殊文字
    .{8,}                 # 最低8文字
  \z/x

  pattern.match?(password)
end

# 3. 名前付きキャプチャの活用
log = '2024-03-15 10:30:45 [ERROR] Failed to connect: timeout'
pattern = /\A
  (?<date>\d{4}-\d{2}-\d{2})\s
  (?<time>\d{2}:\d{2}:\d{2})\s
  \[(?<level>\w+)\]\s
  (?<message>.+)
\z/x

if match = log.match(pattern)
  puts "Date: #{match[:date]}"
  puts "Time: #{match[:time]}"
  puts "Level: #{match[:level]}"
  puts "Message: #{match[:message]}"
end

キャプチャグループを使った柔軟な文字列抽出

キャプチャグループを使用することで、パターンマッチングの結果から必要な部分を効率的に抽出できます。

# 1. 基本的なキャプチャグループ
text = "Name: John Smith, Age: 30"
pattern = /Name: ([\w\s]+), Age: (\d+)/
match = text.match(pattern)

name = match[1]  # => "John Smith"
age = match[2]   # => "30"

# 2. 名前付きキャプチャグループ
pattern = /Name: (?<name>[\w\s]+), Age: (?<age>\d+)/
match = text.match(pattern)

name = match[:name]  # => "John Smith"
age = match[:age]    # => "30"

# 3. キャプチャグループの入れ子
html = '<div class="container"><p>Hello, world!</p></div>'
pattern = /<(\w+)\s*(?:class="([^"]*)")?>(?:<(\w+)>(.+?)<\/\3>)<\/\1>/

if match = html.match(pattern)
  outer_tag = match[1]    # => "div"
  class_name = match[2]   # => "container"
  inner_tag = match[3]    # => "p"
  content = match[4]      # => "Hello, world!"
end

文字列の検証と整形における正規表現活用法

実践的なシーンでの正規表現の活用例を見ていきましょう。

class StringValidator
  EMAIL_PATTERN = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  PHONE_PATTERN = /\A\d{2,4}-\d{2,4}-\d{4}\z/
  URL_PATTERN = /\A(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?\z/

  def self.validate_email(email)
    email.match?(EMAIL_PATTERN)
  end

  def self.validate_phone(phone)
    phone.match?(PHONE_PATTERN)
  end

  def self.validate_url(url)
    url.match?(URL_PATTERN)
  end

  def self.sanitize_filename(filename)
    filename.gsub(/[^0-9A-Za-z.\-]/, '_')
  end

  def self.extract_urls(text)
    text.scan(/https?:\/\/[\w\-\.]+\.\w+(?:\/[\w\-\.\/\?%&=]*)?/)
  end
end

# 使用例
validator = StringValidator
validator.validate_email("user@example.com")  # => true
validator.validate_phone("090-1234-5678")     # => true
validator.sanitize_filename("my file.txt")    # => "my_file.txt"

正規表現を使用する際の重要なポイント:

  1. パフォーマンスへの配慮
  • 過度に複雑な正規表現を避ける
  • 可能な場合はmatch?メソッドを使用
  • 大きな文字列に対する貪欲な量指定子の使用に注意
  1. 可読性の維持
  • 複雑な正規表現はxオプションで整形
  • パターンの意図を明確にするコメントを追加
  • 名前付きキャプチャグループの活用
  1. 保守性の向上
  • 頻繁に使用するパターンは定数化
  • 複雑なバリデーションはクラスにカプセル化
  • テストケースの作成を忘れずに

パフォーマンスを考慮した文字列処理

frozen_string_literalの適切な使用方法

frozen_string_literalは、文字列オブジェクトを不変(イミュータブル)にすることでメモリ使用量を削減し、パフォーマンスを向上させる機能です。

# frozen_string_literal: true

# 基本的な使用方法
str1 = "hello"  # この文字列リテラルは凍結される
str2 = +"hello" # 凍結を解除(ミュータブルな文字列を作成)

# パフォーマンス比較
require 'benchmark'

def with_frozen
  # frozen_string_literal: true が有効な場合
  1000.times { "hello" }
end

def without_frozen
  # frozen_string_literal: true が無効な場合
  1000.times { +"hello" }
end

Benchmark.bm do |x|
  x.report("frozen:   ") { with_frozen }
  x.report("unfrozen: ") { without_frozen }
end

# 文字列の結合時の注意点
def concat_strings
  result = +""  # 結合用の文字列は凍結を解除
  ["a", "b", "c"].each do |str|
    result << str  # 破壊的な結合が可能
  end
  result
end

メモリ効率を考慮した文字列操作の実装

メモリ効率の良い文字列処理を実現するためには、適切なメソッドの選択と実装方法の工夫が重要です。

# 1. 文字列結合の効率化
def inefficient_concat(strings)
  result = ""
  strings.each { |s| result += s }  # 毎回新しい文字列オブジェクトを生成
  result
end

def efficient_concat(strings)
  strings.join  # 一度の操作で結合
end

def buffer_concat(strings)
  result = String.new(capacity: strings.sum(&:length))  # バッファサイズを予約
  strings.each { |s| result << s }
  result
end

# ベンチマーク
require 'benchmark'
strings = Array.new(10000) { "string" }

Benchmark.bm do |x|
  x.report("inefficient: ") { inefficient_concat(strings) }
  x.report("efficient: ") { efficient_concat(strings) }
  x.report("buffer: ") { buffer_concat(strings) }
end

# 2. メモリ使用量の最適化
class StringProcessor
  def initialize(large_text)
    @text = large_text
  end

  # メモリ効率の悪い実装
  def process_inefficient
    lines = @text.split("\n")
    processed = lines.map { |line| line.strip.downcase }
    processed.join("\n")
  end

  # メモリ効率の良い実装
  def process_efficient
    result = String.new(capacity: @text.length)
    @text.each_line do |line|
      result << line.strip.downcase << "\n"
    end
    result.chomp!
  end
end

大量の文字列処理を行う際の最適化テクニック

大規模なテキストデータを処理する際は、以下のような最適化テクニックが有効です。

class LargeTextProcessor
  # 1. チャンク処理による最適化
  def process_by_chunks(file_path, chunk_size = 1024 * 1024)
    File.open(file_path, 'r') do |file|
      while chunk = file.read(chunk_size)
        process_chunk(chunk)
      end
    end
  end

  # 2. ストリーム処理による最適化
  def process_stream(file_path)
    File.open(file_path, 'r') do |file|
      file.each_line do |line|
        process_line(line)
      end
    end
  end

  # 3. 並列処理による最適化
  require 'parallel'

  def parallel_process(file_path, num_threads = 4)
    lines = File.readlines(file_path)
    chunks = lines.each_slice(lines.size / num_threads).to_a

    Parallel.map(chunks) do |chunk|
      process_chunk(chunk.join)
    end.join
  end

  private

  def process_chunk(chunk)
    # チャンク処理のロジック
  end

  def process_line(line)
    # 行処理のロジック
  end
end

# 実装例:大規模なログファイル処理
class LogProcessor
  def initialize(log_path)
    @log_path = log_path
  end

  def analyze_logs
    pattern = /\[(ERROR|WARN|INFO)\]\s+(.+)/
    stats = { error: 0, warn: 0, info: 0 }

    File.open(@log_path, 'r') do |file|
      file.each_line do |line|
        if match = pattern.match(line)
          level = match[1].downcase.to_sym
          stats[level] += 1
        end
      end
    end

    stats
  end
end

パフォーマンス最適化のベストプラクティス:

  1. メモリ使用量の最適化
  • 適切なバッファサイズの設定
  • 不要なオブジェクトの生成を避ける
  • チャンク処理の活用
  1. 処理速度の最適化
  • frozen_string_literalの活用
  • 効率的な文字列結合メソッドの選択
  • 並列処理の検討
  1. コードの保守性との両立
  • パフォーマンス要件の明確化
  • ベンチマークによる効果測定
  • 適切なコメントとドキュメンテーション

実践的なユースケースと解決方法

CSVデータの効率的な処理方法

CSVデータの処理は業務システムでよく必要となる操作です。大規模なCSVファイルを効率的に処理する方法を見ていきましょう。

require 'csv'
require 'stringio'

class CSVProcessor
  def initialize(file_path)
    @file_path = file_path
  end

  # 大規模CSVの効率的な読み込み
  def process_large_csv
    CSV.foreach(@file_path, headers: true) do |row|
      yield row if block_given?
    end
  end

  # メモリ効率の良いCSV生成
  def generate_csv(data)
    CSV.generate(String.new, headers: true) do |csv|
      csv << data.first.keys  # ヘッダー行
      data.each { |row| csv << row.values }
    end
  end

  # CSVデータのバリデーションと整形
  def validate_and_clean_csv
    valid_rows = []
    invalid_rows = []

    CSV.foreach(@file_path, headers: true) do |row|
      cleaned_row = clean_row(row)
      if valid_row?(cleaned_row)
        valid_rows << cleaned_row
      else
        invalid_rows << row
      end
    end
[valid_rows, invalid_rows]

end private def clean_row(row) row.to_h.transform_values do |value| value.to_s.strip.gsub(/[[:space:]]+/, ‘ ‘) end end def valid_row?(row) row.values.none?(&:empty?) end end # 使用例 processor = CSVProcessor.new(‘data.csv’) # 大規模CSVの処理 processor.process_large_csv do |row| # 各行の処理 puts row[‘name’] end

日本語文字列特有の処理への対応

日本語文字列を扱う際は、文字コードや文字種の違いに注意が必要です。

class JapaneseTextProcessor
  KANA_CONVERSION = {
    'あ' => 'ア', 'い' => 'イ', 'う' => 'ウ',
    # ... 他の文字の変換マップ
  }.freeze

  def initialize(text)
    @text = text
  end

  # ひらがなをカタカナに変換
  def to_katakana
    @text.tr('ぁ-ん', 'ァ-ン')
  end

  # カタカナをひらがなに変換
  def to_hiragana
    @text.tr('ァ-ン', 'ぁ-ん')
  end

  # 半角カナを全角カナに変換
  def normalize_width
    @text.unicode_normalize(:nfkc)
  end

  # 文字種の判定
  def character_types
    types = {
      hiragana: 0,
      katakana: 0,
      kanji: 0,
      ascii: 0,
      other: 0
    }

    @text.each_char do |char|
      case char
      when /\p{Hiragana}/
        types[:hiragana] += 1
      when /\p{Katakana}/
        types[:katakana] += 1
      when /\p{Han}/
        types[:kanji] += 1
      when /\p{ASCII}/
        types[:ascii] += 1
      else
        types[:other] += 1
      end
    end

    types
  end

  # 文字列の長さ(マルチバイト対応)
  def text_length
    @text.length
  end

  def display_width
    @text.each_char.sum { |c| c.ascii_only? ? 1 : 2 }
  end
end

# 実践的な使用例
processor = JapaneseTextProcessor.new("こんにちは world 123")
puts processor.character_types
puts processor.display_width

HTMLやXMLの文字列処理テクニック

HTMLやXMLの処理では、適切なパースと生成が重要です。

require 'nokogiri'

class HTMLProcessor
  def initialize(html)
    @html = html
    @doc = Nokogiri::HTML(@html)
  end

  # 安全なHTML生成
  def sanitize_html
    allowed_tags = %w[p a b i strong em]
    allowed_attributes = %w[href title]

    @doc.css('*').each do |node|
      unless allowed_tags.include?(node.name)
        node.replace(node.text)
      end

      node.attributes.each do |attr_name, attr|
        unless allowed_attributes.include?(attr_name)
          attr.remove
        end
      end
    end

    @doc.to_html
  end

  # メタタグの抽出
  def extract_meta_tags
    meta_tags = {}
    @doc.css('meta').each do |meta|
      name = meta['name'] || meta['property']
      meta_tags[name] = meta['content'] if name
    end
    meta_tags
  end

  # リンクの抽出と検証
  def validate_links
    links = []
    @doc.css('a').each do |link|
      href = link['href']
      next unless href

      links << {
        url: href,
        text: link.text,
        valid: valid_url?(href)
      }
    end
    links
  end

  private

  def valid_url?(url)
    uri = URI.parse(url)
    uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
  rescue URI::InvalidURIError
    false
  end
end

# テンプレートエンジンの実装例
class SimpleTemplate
  def initialize(template)
    @template = template
  end

  def render(variables = {})
    result = @template.dup
    variables.each do |key, value|
      result.gsub!(/\{\{\s*#{key}\s*\}\}/, value.to_s)
    end
    result
  end
end

# 使用例
template = SimpleTemplate.new(<<-TEMPLATE)
<div class="user-profile">
  <h1>{{name}}</h1>
  <p>Email: {{email}}</p>
  <p>Age: {{age}}</p>
</div>
TEMPLATE

variables = {
  name: '山田太郎',
  email: 'yamada@example.com',
  age: 30
}

puts template.render(variables)

実践的な文字列処理を行う際の重要なポイント:

  1. エラー処理の考慮
  • 不正なデータへの対応
  • 文字コードの適切な処理
  • 例外処理の実装
  1. パフォーマンスとメモリ使用
  • 大規模データの効率的な処理
  • ストリーム処理の活用
  • 適切なバッファリング
  1. セキュリティへの配慮
  • 入力データのサニタイズ
  • XSS対策の実装
  • 適切なエスケープ処理
  1. 保守性と再利用性
  • モジュール化された設計
  • 適切な抽象化
  • テストの作成