<commit_before>class SafeHtmlValidator < ActiveModel::Validator
  def validate(record)
    record.changes.each do |field_name, (old_value, new_value)|
      check_struct(record, field_name, new_value)
    end
  end

  def check_struct(record, field_name, value)
    if value.respond_to?(:values) # e.g. Hash
      value.values.each { |entry| check_struct(record, field_name, entry) }
    elsif value.respond_to?(:each) # e.g. Array
      value.each { |entry| check_struct(record, field_name, entry) }
    elsif value.is_a?(String)
      check_string(record, field_name, value)
    end
  end

  def check_string(record, field_name, string)
    unless Govspeak::HtmlValidator.new(string).valid?
      error = "cannot include invalid formatting or JavaScript"
      record.errors.add(field_name, error)
    end
  end
end<commit_msg>Running XSS validations is computationally expensive.<commit_after>class SafeHtmlValidator < ActiveModel::Validator
  def validate(record)
    record.changes.each do |field_name, (old_value, new_value)|
      check_struct(record, field_name, new_value)
    end
  end

  def check_struct(record, field_name, value)
    if value.respond_to?(:values) # e.g. Hash
      value.values.each { |entry| check_struct(record, field_name, entry) }
    elsif value.respond_to?(:each) # e.g. Array
      value.each { |entry| check_struct(record, field_name, entry) }
    elsif value.is_a?(String)
      check_string(record, field_name, value)
    end
  end

  def check_string(record, field_name, string)
    if unacceptable_govspeak?(string)
      error = "cannot include invalid formatting or JavaScript"
      record.errors.add(field_name, error)
    end
  end

  private

  def self.cache
    @cache ||= ActiveSupport::Cache::MemoryStore.new
  end

  def unacceptable_govspeak?(string)
    self.class.cache.fetch(string) do
      !Govspeak::HtmlValidator.new(string).valid?
    end
  end
end
{'fraction_non_alphanumeric': 0.10209424083769633, 'fraction_numerical': 0.0, 'mean_word_length': 3.887468030690537, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3207636', 'n_tokens_mistral': 620, 'n_tokens_neox': 606, 'n_words': 143}