Class: Seo::FaClassRewriter

Inherits:
BaseService show all
Defined in:
app/services/seo/fa_class_rewriter.rb

Overview

Note:

The transform is idempotent: re-running on already-canonical HTML
returns changed? == false and contributes no DB writes when used
from a data migration.

Note:

Returns a plain String, not an ActiveSupport::SafeBuffer. Callers
that splice the result into ERB are responsible for marking it safe at
the call site (or letting Rails escape it). The data-migration callers
write the result directly to a DB column, where html-safety doesn't
apply.

Rewrites legacy Font Awesome <i> class strings in stored HTML to the
project's adopted Sharp family (fa-sharp fa-solid / fa-sharp fa-regular).

Background: an in-repo sweep moved every emitted icon to the Sharp variants,
but user-authored content stored in articles.solution / articles.description
(and article_revisions.*) still contains pre-sweep tokens like
<i class="fa-solid …">. Until those rows are rewritten, both the www and
CRM SCSS bundles must keep solid.min.css imported as a backstop. This
service rewrites the stored HTML so that backstop can be dropped.

Mapping (per-<i> class list)

Legacy tokens Rewritten to
fa-solid, fas, fa, fa-duotone, fad fa-sharp fa-solid
fa-regular, far, fa-light, fal, fa-thin, fat fa-sharp fa-regular
already fa-sharp fa-{solid|regular} unchanged (no-op)
fa-brands / fab normalized to fa-brands
fa-kit (custom kit icons) unchanged (rendered via kit script)

Sizing tokens (fa-lg, fa-2xs, …), animation tokens (fa-spin, …),
and the icon name (fa-foo) are preserved.

Defined Under Namespace

Classes: Result

Constant Summary collapse

SOLID_TOKENS =

Family tokens that collapse to fa-sharp fa-solid.

%w[fa-solid fas fa fa-duotone fad].freeze
REGULAR_TOKENS =

Family tokens that collapse to fa-sharp fa-regular (project default).

%w[fa-regular far fa-light fal fa-thin fat].freeze
BRAND_TOKENS =

Brand tokens (collapse to long form, no Sharp variant exists).

%w[fa-brands fab].freeze
FAMILY_TOKENS =

Every family token we recognise; stripped from the class list before
the canonical family is re-emitted.

(SOLID_TOKENS + REGULAR_TOKENS + BRAND_TOKENS + %w[fa-sharp fa-kit]).freeze
CHEAP_PROBE =

Cheap pre-check — skip Nokogiri parsing entirely if no <i> markup
carrying any FA family token is present. Matches both icon-name
form (fa-foo) and standalone short prefixes (fa, fas, far,
fal, fat, fad, fab) so a malformed <i class="far"> with
no icon name still goes through the rewriter rather than being
short-circuited as "no FA content".

/<i\b[^>]*\bclass\s*=\s*["'][^"']*\b(?:fa-[\w-]+|fa[brsltd]?)\b/i

Instance Attribute Summary

Attributes inherited from BaseService

#options

Instance Method Summary collapse

  • #process(html_fragment) ⇒ Result

    Rewrites every <i> in the given HTML fragment to use canonical Sharp family classes per FAMILY_TOKENS and the mapping documented on the class.

Methods inherited from BaseService

#initialize, #log_debug, #log_error, #log_info, #log_warning, #logger, #tagged_logger

Constructor Details

This class inherits a constructor from BaseService

Instance Method Details

#process(html_fragment) ⇒ Result

Note:

Fragment-only by design. Full-document wrappers (<!DOCTYPE>,
<html>, <head>, outer <body>) are stripped on serialization
because the only callers (the articles / article_revisions
data migrations) write fragments to text columns. If a future
caller needs full-document round-tripping, branch on input shape
and use doc.to_html instead of doc.at("body")&.inner_html.

Rewrites every <i> in the given HTML fragment to use canonical Sharp
family classes per FAMILY_TOKENS and the mapping documented on the
class.

The cheap regex pre-check skips the full Nokogiri parse for HTML that
obviously contains no FA <i> markup, so calling this on every row
of a wide table is safe.

Parameters:

  • html_fragment (String, nil)

    HTML fragment to rewrite. Nil
    and blank inputs short-circuit.

Returns:

  • (Result)

    a result struct: html_out is the rewritten HTML
    (plain String — not html-safe; see class note), changed? indicates
    whether any <i> was rewritten, and count is the number of
    rewrites performed.

Raises:

  • (Nokogiri::XML::SyntaxError)

    only on pathologically malformed
    input — Nokogiri::HTML is normally lenient and degrades gracefully.



96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'app/services/seo/fa_class_rewriter.rb', line 96

def process(html_fragment)
  return Result.new(html_out: html_fragment) if html_fragment.blank?
  return Result.new(html_out: html_fragment) unless html_fragment.match?(CHEAP_PROBE)

  doc = Nokogiri::HTML(html_fragment)
  count = rewrite_i_tags(doc)

  if count > 0
    html_out = doc.at("body")&.inner_html.presence
    Result.new(html_out: html_out, changed: true, count: count)
  else
    Result.new(html_out: html_fragment)
  end
end