Удаление HTML из текста, содержащего символы <и>, с помощью люфы и нокогири - PullRequest
1 голос
/ 10 июня 2011

Я полагаю, что это достаточно часто, так что это решаемая проблема, но, будучи новичком в Loofah и Nokogiri, я пока не нашел решения.

Я использую Loofah, скруббер HTMLбиблиотека, которая обертывает Nokogiri, чтобы вычистить некоторый текст HTML для отображения.Однако этот текст иногда встречается с такими вещами, как адреса электронной почты и между < и > символами, например, < foo@domain.com >.Люфа рассматривает это как тег HTML или XML и удаляет его из текста.

Есть ли способ предотвратить это, в то же время выполняя хорошую работу по удалению реальных тегов?

Редактировать: Вот неудачный тестовый пример:

require 'test/unit'
require 'test/unit/ui/console/testrunner'
require 'nokogiri'

MAGICAL_REGEXP = /<([^(?:\/|!\-\-)].*)>/

def filter_html(content)
  # Current approach in a gist: We capture content enclosed in angle brackets.
  # Then, we check if the excerpt right after the opening bracket is a valid HTML
  # tag. If it's not, we substitute the matched content (which is the captured
  # content enclosed in angle brackets) for the captured content enclosed in
  # the HTML entities for the angle brackets. This does not work with nested
  # HTML tags, since regular expressions are not meant for this.

  content.to_s.gsub(MAGICAL_REGEXP) do |excerpt|
    capture = $1
    Nokogiri::HTML::ElementDescription[capture.split(/[<> ]/).first] ? excerpt : "&lt;#{capture}&gt;"
  end
end

class HTMLTest < Test::Unit::TestCase
  def setup
    @raw_html = <<-EOS
<html>
<foo@bar.baz>
<p><foo@<b class="highlight">bar</b>.baz></p>
<p>
<foo@<b class="highlight">bar</b>.baz>
</p>
< don't erase this >
</html>
EOS

    @filtered_html = <<-EOS
<html>
&lt;foo@bar.baz&gt;
<p>&lt;foo@<b class="highlight">bar</b>.baz&gt;</p>
<p>
&lt;foo@<b class="highlight">bar</b>.baz&gt;
</p>
&lt; don't erase this &gt;
</html>
EOS
  end

  def test_filter_html
    assert_equal(@filtered_html, filter_html(@raw_html))
  end
end

# Can you make this test pass?
Test::Unit::UI::Console::TestRunner.run(HTMLTest)

В настоящее время мы используем довольно злобное хакерство регулярных выражений, чтобы попытаться выполнить это, но, как говорится в комментарии выше, это не работает длятеги "вложенные" внутри не тегов.И мы действительно хотим сохранить элементы <b class="highlight">.

В приведенном ниже примере не используется Loofah, но само приложение используется в других местах, поэтому добавить его сюда будет несложно.Мы просто не уверены, какие параметры конфигурации нам следует использовать, если таковые имеются.

1 Ответ

2 голосов
/ 15 июня 2011

Поскольку основной проблемой были HTML-теги, заключенные в угловые скобки сущностей HTML - что полностью искажено Nokogiri - мы решили эту проблему, просто удалив вышеупомянутые HTML-теги, минуя угловые скобки не-HTML-тегов, а затем поместив HTML-теги назад. Звучит немного хакерски, но работает отлично. Нашей первой целью было избежать адресов электронной почты, заключенных в угловые скобки, но этот подход (предположительно) работает для любого вида текста.

# Does not run on ruby 1.9

require 'test/unit'
require 'test/unit/ui/console/testrunner'
require 'nokogiri'
require 'active_support/secure_random'

def filter_html(content)
  # Used to mark highlighted words.
  random_hex = SecureRandom.hex(6)

  # Remove highlighting.
  highlighted_terms = []
  without_highlighting = content.to_s.gsub(/<b class="highlight">(.*?)<\/b>/) do |match|
    highlighted_terms << $1
    "highlight-#{random_hex}:#{$1}"
  end

  # Escape non-HTML angle brackets.
  escaped_content = without_highlighting.to_s.gsub(/<(?:\s*\/)?([^!\-\-].*?)>/) do |excerpt|
    capture = $1
    tag = capture.split(/[^a-zA-Z1-6]/).reject(&:empty?).first
    !!Nokogiri::HTML::ElementDescription[tag] ? excerpt : "&lt;#{capture}&gt;"
  end

  # Add highlighting back.
  highlighted_terms.uniq.each do |term|
    escaped_content.gsub!(/highlight-#{random_hex}:(#{term})/) do |match|
      "<b class=\"highlight\">#{$1}</b>"
    end
  end

  escaped_content
end

class HTMLTest < Test::Unit::TestCase
  def setup
    @raw_html = <<-EOS
      <html>
        <foo@bar.baz>
        <p><foo@<b class="highlight">bar</b>.baz></p>
        <p>
          <foo@<b class="highlight">bar</b>.baz>
        </p>
        <    don't erase this   >
      </html>
    EOS

    @filtered_html = <<-EOS
      <html>
        &lt;foo@bar.baz&gt;
        <p>&lt;foo@<b class="highlight">bar</b>.baz&gt;</p>
        <p>
          &lt;foo@<b class="highlight">bar</b>.baz&gt;
        </p>
        &lt;    don't erase this   &gt;
      </html>
    EOS
  end

  def test_filter_html
    assert_equal(@filtered_html, filter_html(@raw_html))
  end
end

# It passes!
Test::Unit::UI::Console::TestRunner.run(HTMLTest)
...