Skip to content

Commit 8da3d97

Browse files
committed
Migrate Nokogiri::HTML to Nokogiri::HTML5
HTML5 parser matches browser parsing semantics for edge cases (e.g. block elements inside <p> auto-close the paragraph per spec). All inputs are well-formed kramdown output, so no behavioral change on production content (verified via html_diff.py: zero diffs). Changes: - Switch all six Nokogiri::HTML / HTML.fragment calls to HTML5. - Add test for HTML5 parse-tree shape when <figure> is nested in <p>. - Update comment in FootnotePreviewInjector to match new parser name.
1 parent c7ba5ca commit 8da3d97

4 files changed

Lines changed: 23 additions & 6 deletions

File tree

_plugins/src/infrastructure/footnote_preview_injector.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ module Infrastructure
4242
# to strip the preview when reusing rendered HTML (feeds, plain-text, etc).
4343
#
4444
# The full HTML string is never round-tripped through Nokogiri to avoid
45-
# DOCTYPE mangling: Nokogiri::HTML is used read-only for extraction;
45+
# DOCTYPE mangling: Nokogiri::HTML5 is used read-only for extraction;
4646
# injection uses targeted string replacement on the original string.
4747
module FootnotePreviewInjector
4848
Text = Jekyll::Infrastructure::TextProcessingUtils
@@ -68,7 +68,7 @@ module FootnotePreviewInjector
6868
def self.inject(html)
6969
return html unless html.include?('class="footnote"')
7070

71-
doc = Nokogiri::HTML(html)
71+
doc = Nokogiri::HTML5(html)
7272
footnote_map = build_footnote_map(doc)
7373
return html if footnote_map.empty?
7474

_plugins/src/infrastructure/text_processing_utils.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ module TextProcessingUtils
2222
def self.clean_text_from_html(html_content)
2323
return '' if html_content.nil? || html_content.strip.empty?
2424

25-
doc = Nokogiri::HTML(html_content.to_s)
25+
doc = Nokogiri::HTML5(html_content.to_s)
2626
doc.xpath('//script | //style').remove # Remove script and style elements
2727
doc.text.gsub(/\s+/, ' ').strip # Get text from remaining, normalize whitespace
2828
end
@@ -118,7 +118,7 @@ def self.slugify(text)
118118
# hidden preview text never leaks into plain-text output.
119119
def self.strip_tags(html)
120120
cleaned = strip_link_previews(strip_footnote_previews(html))
121-
Nokogiri::HTML.fragment(cleaned).text
121+
Nokogiri::HTML5.fragment(cleaned).text
122122
end
123123

124124
# Removes book-link hover-preview markup (see BookPreviewRenderer), including

_plugins/src/seo/seo_meta_generator.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def raw_title
120120
end
121121

122122
def decode_html_entities(text)
123-
Nokogiri::HTML.fragment(text.to_s).text
123+
Nokogiri::HTML5.fragment(text.to_s).text
124124
end
125125

126126
def site_title
@@ -168,7 +168,7 @@ def site_description
168168

169169
def strip_html(text)
170170
cleaned = Infrastructure::TextProcessingUtils.strip_link_previews(text)
171-
Nokogiri::HTML.fragment(cleaned).text.gsub(/\s+/, ' ').strip
171+
Nokogiri::HTML5.fragment(cleaned).text.gsub(/\s+/, ' ').strip
172172
end
173173

174174
# --- Image ---

_tests/src/infrastructure/test_footnote_preview_injector.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,23 @@ def test_inject_flattens_block_elements_to_classed_spans
106106
refute_match(/<(?:figure|blockquote|figcaption|p)\b/, preview)
107107
end
108108

109+
def test_inject_figure_nested_in_paragraph
110+
# kramdown wraps cited-quote figures in a surrounding <p>. The HTML5
111+
# parser auto-closes the <p> before <figure> (per spec), ejecting it
112+
# as a sibling. flatten_blocks converts the resulting empty <p> tags
113+
# to empty fnp-p spans (harmless — they render nothing). Verify the
114+
# preview preserves all content regardless of the parse-tree shape.
115+
body = '<p><figure class="cited-quote">' \
116+
'<blockquote><p>The quote text.</p></blockquote>' \
117+
'<figcaption>—<span class="citation">Author.</span></figcaption>' \
118+
'</figure></p>'
119+
result = Injector.inject(footnote_html(id: 'note', footnote_body: body))
120+
preview = extract_preview(result).to_s
121+
refute_match(/<(?:figure|blockquote|figcaption|p)\b/, preview)
122+
assert_includes preview, 'The quote text.'
123+
assert_includes preview, 'Author.'
124+
end
125+
109126
def test_inject_citedquote_full_fidelity
110127
# The real citedquote structure: figure > blockquote + figcaption,
111128
# where figcaption holds span.citation with <cite> and <a>. Existing

0 commit comments

Comments
 (0)