From 11149a52842ad16d48f8678109ff63d183533013 Mon Sep 17 00:00:00 2001
From: Ridge Kimani
#{katex('y = xA^T + b')}
) + assert_equal 1, math_nodes.length + assert_equal 'y = xA^T + b', tex_of(math_nodes.first) + assert_includes math_nodes.first.to_s, '#{katex(nil)}
) + assert_equal 'Pay $5 and $10 for $x$ and $$y$$, not $x$.
" do
+ assert_equal '$x$', filter_output.at_css('code').to_s
+ end
+ end
+ end
+end
From 9c97c92eb30a0a6e21576721e6f86a5518734acc Mon Sep 17 00:00:00 2001
From: Simon Legner
Date: Thu, 15 Oct 2026 17:13:51 +0200
Subject: [PATCH 2/4] math: handle Sphinx markup only
Drop the MathJax 2/3, KaTeX and $-delimiter paths: no scraper needs
them, and PyTorch's KaTeX output already ships MathML.
---
docs/filter-reference.md | 2 +-
lib/docs/filters/core/math.rb | 122 +++++-------------------
lib/docs/filters/pytorch/clean_html.rb | 2 +
test/lib/docs/filters/core/math_test.rb | 91 +-----------------
4 files changed, 28 insertions(+), 189 deletions(-)
diff --git a/docs/filter-reference.md b/docs/filter-reference.md
index a9d9b8916a..00139d524b 100644
--- a/docs/filter-reference.md
+++ b/docs/filter-reference.md
@@ -74,7 +74,7 @@ The `call` method must return either `doc` or `html`, depending on the type of f
## Core filters
* [`ContainerFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/container.rb) — changes the root node of the document (remove everything outside)
-* [`MathFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/math.rb) — converts math markup (Sphinx/MathJax `\(...\)` and `\[...\]`, MathJax ` and type="math/tex; mode=display"
- # - MathJax 3: (keeps its assistive MathML, if any)
- # - KaTeX: pre-rendered nodes (re-rendered from their TeX source)
- # - Dollar delimiters ($...$ and $$...$$) in text, only when context[:math_dollars] is true
- # Existing elements are left untouched.
- #
- # This filter must run before CleanHtmlFilter, which removes '
- assert_equal 1, math_nodes.length
- assert_equal 'x < y', tex_of(math_nodes.first)
- assert_nil math_nodes.first['display']
- assert_empty filter_output.css('script, .MathJax_Preview')
- end
-
- it "replaces '
- assert_equal 'block', math_nodes.first['display']
- end
- end
-
- context "MathJax 3 markup" do
- it "replaces with its assistive MathML" do
- @body = 'x
'
- assert_equal 'x
', filter_output_string
- end
-
- it "leaves without assistive MathML untouched" do
- @body = ' '
- assert_equal @body, filter_output_string
- end
- end
-
- context "KaTeX markup" do
- def katex(tex, mathml: true)
- annotation = tex ? %(#{tex} ) : ''
- math = mathml ? %(z #{annotation} ) : ''
- %(#{math})
- end
-
- it "re-renders the TeX source of inline math" do
- @body = %(#{katex('y = xA^T + b')}
)
- assert_equal 1, math_nodes.length
- assert_equal 'y = xA^T + b', tex_of(math_nodes.first)
- assert_includes math_nodes.first.to_s, 'y '
- assert_empty filter_output.css('.katex, .katex-html, .katex-mathml')
- end
-
- it "re-renders the TeX source of display math" do
- @body = %(#{katex('y')})
- assert_equal 'block', math_nodes.first['display']
- assert_empty filter_output.css('.katex-display')
- end
-
- it "keeps the existing MathML when there's no TeX source" do
- @body = %(#{katex(nil)}
)
- assert_equal 'z
', filter_output_string
- end
-
- it "leaves nodes without MathML or TeX source untouched" do
- @body = katex(nil, mathml: false)
- assert_equal @body, filter_output_string
- end
- end
-
- context "dollar delimiters" do
- before { @body = 'Pay $5 and $10 for $x$ and $$y$$, not $x$.
' }
-
- it "are ignored by default" do
- assert_equal @body, filter_output_string
- end
-
- context "when context[:math_dollars] is true" do
- before { context[:math_dollars] = true }
-
- it "are rendered" do
- assert_equal %w(x y), math_nodes.map { |node| tex_of(node) }
- assert_equal 'block', math_nodes.last['display']
- end
-
- it "aren't mistaken for currency" do
- assert_includes filter_output_string, 'Pay $5 and $10 for '
- end
-
- it "aren't rendered inside " do
- assert_equal '$x$', filter_output.at_css('code').to_s
- end
- end
- end
end
From ece781cacf1f4762cbb312894e938582080f225e Mon Sep 17 00:00:00 2001
From: Simon Legner
Date: Thu, 15 Oct 2026 17:14:15 +0200
Subject: [PATCH 3/4] math: make the filter opt-in
Remove it from the default filter stack and enable it for the Sphinx
scrapers whose pages contain math: NumPy, scikit-learn, scikit-image,
Polars and statsmodels.
---
docs/filter-reference.md | 2 +-
lib/docs/core/scraper.rb | 2 +-
lib/docs/scrapers/numpy.rb | 2 +-
lib/docs/scrapers/polars.rb | 2 +-
lib/docs/scrapers/scikit_image.rb | 2 +-
lib/docs/scrapers/scikit_learn.rb | 2 +-
lib/docs/scrapers/statsmodels.rb | 2 +-
7 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/docs/filter-reference.md b/docs/filter-reference.md
index 00139d524b..04114fa600 100644
--- a/docs/filter-reference.md
+++ b/docs/filter-reference.md
@@ -74,7 +74,7 @@ The `call` method must return either `doc` or `html`, depending on the type of f
## Core filters
* [`ContainerFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/container.rb) — changes the root node of the document (remove everything outside)
-* [`MathFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/math.rb) — converts Sphinx/MathJax math markup (`\(...\)` and `\[...\]`) to native MathML using [KaTeX](https://katex.org/), which requires a JavaScript runtime such as Node.js
+* [`MathFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/math.rb) — converts Sphinx/MathJax math markup (`\(...\)` and `\[...\]`) to native MathML using [KaTeX](https://katex.org/), which requires a JavaScript runtime such as Node.js; not in the default stack, add `math` to a scraper's `html_filters` to use it
* [`CleanHtmlFilter`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/filters/core/clean_html.rb) — removes HTML comments, `