Class: Seo::FaClassRewriter
- Inherits:
-
BaseService
- Object
- BaseService
- Seo::FaClassRewriter
- Defined in:
- app/services/seo/fa_class_rewriter.rb
Overview
The transform is idempotent: re-running on already-canonical HTML
returns changed? == false and contributes no DB writes when used
from a data migration.
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
Instance Method Summary collapse
-
#process(html_fragment) ⇒ Result
Rewrites every
<i>in the given HTML fragment to use canonical Sharp family classes perFAMILY_TOKENSand 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
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.
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 = (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 |