Как удалить повторяющиеся элементы HTML? - PullRequest
0 голосов
/ 14 апреля 2020

У меня есть следующий вход. html, который имеет несколько повторяющихся <p>..</p> элементов. Например, <p><strong><span style="color:blue; font-size: 24px;">Midmarket</span></strong></p> повторяется 3 раза.

input. html

<!DOCTYPE html>
<html>
<head>
<title>Example</title>
</head>
<body>
<p><strong><span style="color:blue; font-size: 24px;">Midmarket</span></strong></p>
<p><strong><span style="color:green; font-size: 20px;">Car</span></strong></p>
<p>France</p>
<p>2178</p>
<p><strong><span style="color:blue; font-size: 24px;">Midmarket</span></strong></p>
<p><strong><span style="color:green; font-size: 20px;">Car</span></strong></p>
<p>Germany</p>
<p>888</p>
<p><strong><span style="color:blue; font-size: 24px;">Midmarket</span></strong></p>
<p><strong><span style="color:red; font-size: 20px;">Mon</span></strong></p>
<p>Germany</p>
<p>921</p>
<p><strong><span style="color:#F87217; font-size: 24px;">Government</span></strong></p>
<p><strong><span style="color:green; font-size: 20px;">Car</span></strong></p>
<p>Canada</p>
<p>1618.5</p>
<p><strong><span style="color:#F87217; font-size: 24px;">Government</span></strong></p>
<p><strong><span style="color:green; font-size: 20px;">Car</span></strong></p>
<p>Germany</p>
<p>1321</p>
<p><strong><span style="color:#F87217; font-size: 24px;">Government</span></strong></p>
<p><strong><span style="color:green; font-size: 20px;">Car</span></strong></p>
<p>Germany</p>
<p>1513</p>
<p><strong><span style="color:#F87217; font-size: 24px;">Government</span></strong></p>
<p><strong><span style="color:red; font-size: 20px;">Mon</span></strong></p>
<p>France</p>
<p>1899</p>
<p><strong><span style="color:#C48189; font-size: 24px;">Enterprise</span></strong></p>
<p><strong><span style="color:red; font-size: 20px;">Mon</span></strong></p>
<p>Canada</p>
<p>2665.5</p>
<p><strong><span style="color:#C48189; font-size: 24px;">Enterprise</span></strong></p>
<p><strong><span style="color:red; font-size: 20px;">Mon</span></strong></p>
<p>Canada</p>
<p>345</p>
</body>
</html>

, и я хотел бы удалить повторяющиеся элементы <p>..</p>, оставляя только первое вхождение каждого элемента.

out. html

<html>
<body>

<p><strong><span style='font-size:24px;color:blue;'>Midmarket</span></strong></p>
<p><strong><span style='font-size:20px;color:green;'>Car</span></strong></p>
<p>France</p>
<p>2178</p>
<p>Germany</p>
<p>888</p>
<p><strong><span style='font-size:20px;color:red;'>Mon</span></strong></p>
<p>Germany</p>
<p>921</p>
<p><strong><span style='font-size:18px;color:#F87217;'>Government</span></strong></p>
<p><strong><span style='font-size:20px;color:green;'>Car</span></strong></p>
<p>Canada</p>
<p>1618.5</p>
<p>Germany</p>
<p>1321</p>
<p>1513</p>
<p><strong><span style='font-size:20px;color:red;'>Mon</span></strong></p>
<p>France</p>
<p>1899</p>
<p><strong><span style='font-size:18px;color:#C48189;'>Enterprise</span></strong></p>
<p><strong><span style='font-size:20px;color:red;'>Mon</span></strong></p>
<p>Canada</p>
<p>2665.5</p>
<p>345</p>

</body>
</html>

Я пытаюсь добавить элементы <p> в массив, а затем вызываю uniq, но это не работает, потому что длина массива a соответствует длине массива b

Как я могу это сделать?

require 'nokogiri'

doc = File.open("input.html") { |f| Nokogiri::HTML(f) }

a=[]
doc.css("p").each{|el| a.push(el) }

b = a.uniq

Ответы [ 2 ]

1 голос
/ 15 апреля 2020

Ваш HTML необходимо уменьшить; нет причин иметь более одного дубликата для тестирования кода, который создает уникальные значения.

Кроме того, чтобы сделать более очевидным, что удаляется, я массировал HTML, сортируя узлы для группировки дубликатов.

Я бы начал с этого:

require 'nokogiri'
require 'set'

doc = Nokogiri::HTML(<<EOT)
<body>
<html>
<p><strong><span style="color:#C48189; font-size: 24px;">Enterprise</span></strong></p>
<p><strong><span style="color:#C48189; font-size: 24px;">Enterprise</span></strong></p>
<p><strong><span style="color:#F87217; font-size: 24px;">Government</span></strong></p>
<p><strong><span style="color:#F87217; font-size: 24px;">Government</span></strong></p>
<p><strong><span style="color:blue; font-size: 24px;">Midmarket</span></strong></p>
<p><strong><span style="color:blue; font-size: 24px;">Midmarket</span></strong></p>
<p><strong><span style="color:green; font-size: 20px;">Car</span></strong></p>
<p><strong><span style="color:green; font-size: 20px;">Car</span></strong><p>
<p><strong><span style="color:red; font-size: 20px;">Mon</span></strong><p>
<p><strong><span style="color:red; font-size: 20px;">Mon</span></strong><p>
<p>888</p>
<p>921</p>
<p>Canada</p>
<p>Canada</p>
<p>France</p>
<p>France</p>
<p>Germany</p>
<p>Germany</p>
</body>
</html>
EOT

Перед запуском HTML содержит:

doc.search('p').size # => 21

Вот как удалить дубликаты:

p_nodes = Set.new

doc.search('p').each { |p|  
  if p_nodes.include?(p.to_html)
    p.remove
  else
    p_nodes.add(p.to_html)
  end
}

После запуска кода HTML содержит:

doc.search('p').size # => 12

В результате:

puts doc.to_html

# >> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
# >> <html><body>
# >> 
# >> <p><strong><span style="color:#C48189; font-size: 24px;">Enterprise</span></strong></p>
# >> 
# >> <p><strong><span style="color:#F87217; font-size: 24px;">Government</span></strong></p>
# >> 
# >> <p><strong><span style="color:blue; font-size: 24px;">Midmarket</span></strong></p>
# >> 
# >> <p><strong><span style="color:green; font-size: 20px;">Car</span></strong></p>
# >> <p><strong><span style="color:green; font-size: 20px;">Car</span></strong></p>
# >> <p>
# >> </p>
# >> <p><strong><span style="color:red; font-size: 20px;">Mon</span></strong></p>
# >> <p>888</p>
# >> <p>921</p>
# >> <p>Canada</p>
# >> 
# >> <p>France</p>
# >> 
# >> <p>Germany</p>
# >> 
# >> 
# >> </body></html>

Set выполняет маги c , Набор похож на Ха sh, только каждый элемент не имеет значения, связанного с ним, это только ключ. И, как Ha sh, набор может содержать только один экземпляр этого элемента, который, в данном случае, является текстовой версией этого конкретного <p> узла. В результате получается, что каждый узел проверяется на соответствие p_nodes, чтобы определить, существует ли он, и удаляется, если он есть, в противном случае он добавляется до тех пор, пока не будет проверен каждый узел <p>.


Примечание : Nokogiri не удаляет узлы Text, содержащие концы строк, которые следуют за узлами <p>, поэтому есть пробелы. Браузеры "проглатывают пустые места", поэтому полученная отрисовка HTML должна выглядеть одинаково, только без дубликатов.

После запуска вывод показывает:

# >> <p><strong><span style="color:green; font-size: 20px;">Car</span></strong></p>
# >> <p><strong><span style="color:green; font-size: 20px;">Car</span></strong></p>
# >> <p>
# >> </p>

Я думаю, что это результат Нокогири видит, что HTML искажен. Есть три строки, которые не имеют правильных закрывающих тегов </p>

<p><strong><span style="color:green; font-size: 20px;">Car</span></strong><p>
<p><strong><span style="color:red; font-size: 20px;">Mon</span></strong><p>
<p><strong><span style="color:red; font-size: 20px;">Mon</span></strong><p>

, и в процессе тихого исправления HTML добавляется паразитная пара. Это происходит потому, что люди, генерирующие HTML, не обращают внимания на детали.

Эти конкретные узлы являются причиной вышеуказанного пустого узла <p> и дублированных строк "Cars" выше.

Обычно Nokogiri будет отмечать ошибки разметки, если они существенны, с использованием Nokogiri::XML::Document#errors метод, но <p> имеют необязательные закрывающие теги, так что это может быть утечка через трещину.

Исправление искаженных линий не приводит к дублированию "Cars" или пустым <p> узел, от:

# >> <p><strong><span style="color:green; font-size: 20px;">Car</span></strong></p>
# >> <p><strong><span style="color:green; font-size: 20px;">Car</span></strong></p>
# >> <p>
# >> </p>

до:

# >> <p><strong><span style="color:green; font-size: 20px;">Car</span></strong></p>
# >> 
# >> <p><strong><span style="color:red; font-size: 20px;">Mon</span></strong></p>
# >> 

Иногда нам необходимо предварительно обработать документы, чтобы исправить их, прежде чем передавать их в Nokogiri, если разметка слишком сильно искажена. Нокогири может делать только так много.

Вот что происходит после того, как парсер исправляет ошибки; Это правильная разметка:

doc = Nokogiri::HTML(<<EOT)
<p>foo</p>
EOT

doc.to_html
# => "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n" +
#    "<html><body>\n" +
#    "<p>foo</p>\n" +
#    "</body></html>\n"

, которую Nokogiri не исправляет, по сравнению с:

doc = Nokogiri::HTML(<<EOT)
<p>foo<p>
EOT

doc.to_html
# => "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n" +
#    "<html><body>\n" +
#    "<p>foo</p>\n" +
#    "<p>\n" +
#    "</p>\n" +
#    "</body></html>\n"

Обратите внимание, как Nokogiri пришлось закрыть первый и второй <p> теги, что привело к постороннему <p></p> пара, а не в оригинальном документе. Это будет отражено в отрисованном выводе страницы, потому что браузер отобразит его как разрыв абзаца, но вы должны написать код для управления этим, как сказано выше. С того момента, как Нокогири вставил закрывающий </p> в конец документа, вполне вероятно, что отображение будет отличаться от ожидаемого, следовательно, необходимо предварительно обработать и исправить входящий HTML.

1 голос
/ 14 апреля 2020

Это может привести вас в правильном направлении, на вашем примере неясно, хотите ли вы, например, удалить второй автомобиль, но вопрос заключается в том, чтобы удалить дублированные p's

require 'nokogiri' 

doc = Nokogiri::HTML(<<EOT)
<html>
<body>
  <p><strong><span style="color:blue; font-size: 24px;">Midmarket</span></strong></p>
  <p><strong><span style="color:green; font-size: 20px;">Car</span></strong><p>
  <p>France</p>
  <p>2178</p>
  <p><strong><span style="color:blue; font-size: 24px;">Midmarket</span></strong></p>
  <p><strong><span style="color:green; font-size: 20px;">Car</span></strong><p>
  <p><strong><span style="background-color:yellow; font-size: 16px;">Germany</span></strong></p>
  <p>888</p>
  <p><strong><span style="color:blue; font-size: 24px;">Midmarket</span></strong></p>
  <p><strong><span style="color:red; font-size: 20px;">Mon</span></strong><p>
  <p><strong><span style="background-color:yellow; font-size: 16px;">Germany</span></strong></p>
  <p>921</p>
  <p><strong><span style="color:#F87217; font-size: 24px;">Government</span></strong></p>
  <p><strong><span style="color:green; font-size: 20px;">Car</span></strong></p>
  <p>Canada</p>
  <p>1618.5</p>
  <p><strong><span style="color:#F87217; font-size: 24px;">Government</span></strong></p>
  <p><strong><span style="color:green; font-size: 20px;">Car</span></strong><p>
  <p><strong><span style="background-color:yellow; font-size: 16px;">Germany</span></strong></p>
  <p>1321</p>
  <p><strong><span style="color:#F87217; font-size: 24px;">Government</span></strong></p>
  <p><strong><span style="color:green; font-size: 20px;">Car</span></strong><p>
  <p><strong><span style="background-color:yellow; font-size: 16px;">Germany</span></strong></p>
  <p>1513</p>
  <p><strong><span style="color:#F87217; font-size: 24px;">Government</span></strong></p>
  <p><strong><span style="color:red; font-size: 20px;">Mon</span></strong><p>
  <p>France</p>
  <p>1899</p>
  <p><strong><span style="color:#C48189; font-size: 24px;">Enterprise</span></strong></p>
  <p><strong><span style="color:red; font-size: 20px;">Mon</span></strong><p>
  <p>Canada</p>
  <p>2665.5</p>
  <p><strong><span style="color:#C48189; font-size: 24px;">Enterprise</span></strong></p>
  <p><strong><span style="color:red; font-size: 20px;">Mon</span></strong><p>
  <p>Canada</p>
  <p>345</p>
</body>
</html>
EOT
printed = []
doc.xpath('//p', '//strong//span').each do |text|
  content = text.content.gsub(/\s/, '')
  if !content.empty? && !printed.include?(content)
    printed.push(content)
    p content
  end
end
...