Project

General

Profile

Defect #43730 » patch-issue-43730-fix-collapse-macro-invalid-html.diff

Akihiro Kubota, 2026-05-06 10:05

View differences:

app/helpers/application_helper.rb
949 949
        send method_name, txt, project, obj, attr, only_path, options
950 950
      end
951 951
    end
952
    unwrap_paragraphs_containing_block_macros(text)
952 953
    parse_headings(text, project, obj, attr, only_path, options)
953 954

  
954 955
    if @parsed_headings.any?
......
1468 1469
    end
1469 1470
  end
1470 1471

  
1472
  # Fixes invalid HTML where block-level elements from macros (e.g. collapse)
1473
  # are nested inside <p> tags. Replaces the <p></p> with <div></div> so that
1474
  # consecutive paragraphs remain visually separated as block-level elements.
1475
  # Uses a pattern that treats <div class="collapsed-text">...</div> as atomic
1476
  # so that </p> inside the div's content does not end the outer match early.
1477
  def unwrap_paragraphs_containing_block_macros(text)
1478
    collapsed_div = /<div[^>]*class="[^"]*collapsed-text[^"]*"[^>]*>(?:(?!<\/div>).)*<\/div>/m
1479
    non_collapsed = /(?!<\/p>)(?!<div[^>]*class="[^"]*collapsed-text)./m
1480
    text.gsub!(
1481
      /<p>(#{non_collapsed}*(?:#{collapsed_div}#{non_collapsed}*)+)<\/p>/m,
1482
      '<div>\1</div>'
1483
    )
1484
  end
1485

  
1471 1486
  TOC_RE = /<p>\{\{((<|&lt;)|(>|&gt;))?toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
1472 1487

  
1473 1488
  # Renders the TOC with given headings
test/unit/lib/redmine/wiki_formatting/macros_test.rb
314 314
    end
315 315
  end
316 316

  
317
  COLLAPSE_INVALID_HTML_RE = /<p>(?:(?!<\/p>).)*<div[^>]*class="[^"]*collapsed-text/m
318

  
319
  # Example 1: text between collapses in the same paragraph
320
  def test_macro_multiple_collapse_with_text_between_should_not_generate_div_inside_p
321
    text = <<~RAW
322
      1 {{collapse(1)
323
      content 1
324
      }}
325
      2 {{collapse(2)
326
      content 2
327
      }}
328
      3 {{collapse(3)
329
      content 3
330
      }}
331
    RAW
332
    with_locale 'en' do
333
      with_settings :text_formatting => 'textile' do
334
        assert_no_match(COLLAPSE_INVALID_HTML_RE, textilizable(text))
335
      end
336
    end
337
  end
338

  
339
  # Example 2: adjacent collapses with no text between
340
  def test_macro_multiple_collapse_adjacent_should_not_generate_div_inside_p
341
    text = <<~RAW
342
      {{collapse(1)
343
      content 1
344
      }}
345
      {{collapse(2)
346
      content 2
347
      }}
348
      {{collapse(3)
349
      content 3
350
      }}
351
    RAW
352
    with_locale 'en' do
353
      with_settings :text_formatting => 'textile' do
354
        assert_no_match(COLLAPSE_INVALID_HTML_RE, textilizable(text))
355
      end
356
    end
357
  end
358

  
359
  # Example 3: multiple groups of collapses separated by a blank line
360
  def test_macro_multiple_collapse_groups_should_not_generate_div_inside_p
361
    text = <<~RAW
362
      1 {{collapse(1)
363
      content 1
364
      }}
365
      2 {{collapse(2)
366
      content 2
367
      }}
368

  
369
      3 {{collapse(3)
370
      content 3
371
      }}
372
      4 {{collapse(4)
373
      content 4
374
      }}
375
    RAW
376
    with_locale 'en' do
377
      with_settings :text_formatting => 'textile' do
378
        assert_no_match(COLLAPSE_INVALID_HTML_RE, textilizable(text))
379
      end
380
    end
381
  end
382

  
317 383
  def test_macro_child_pages
318 384
    expected =
319 385
      "<p><ul class=\"pages-hierarchy\">\n" \
(7-7/12)