Как удалить повторяющиеся вложенные теги с помощью Nokogiri - PullRequest
2 голосов
/ 12 февраля 2020

У меня есть HTML с вложенными повторяющимися тегами:

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  </head>
  <body>
    <div>
      <div>
        <div>
          <p>Some text</p>
        </div>  
      </div>
    </div>
  </body>
</html> 

Я хочу удалить вложенные повторяющиеся div s, которые не имеют никаких атрибутов. Получившийся HTML должен выглядеть следующим образом:

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  </head>
  <body>
    <div>
      <p>Some text</p>
    </div>  
  </body>
</html> 

Как это можно сделать, используя Нокогири или чистый Ruby?

Ответы [ 2 ]

1 голос
/ 13 февраля 2020

Исходя из того, как структурирован ваш HTML, вы должны начать:

require 'nokogiri'

doc = Nokogiri::HTML(<<EOT)
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  </head>
  <body>
    <div>
      <div>
        <div>
          <p>Some text</p>
        </div>  
      </div>
    </div>
  </body>
</html> 
EOT

dd = doc.at('div div').parent
dp = dd.at('div p')
dd.children.unlink
dp.parent = dd

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

puts doc.to_html

# >> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
# >> <html>
# >>   <head>
# >>     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
# >>   </head>
# >>   <body>
# >>     <div><p>Some text</p></div>
# >>   </body>
# >> </html>

dd - это parent для двух последовательных тегов div, другими словами, это первый div в цепочке.

dp - это узел p в конце этой цепочки.

dd.children - это NodeSet, содержащий children из dd, вплоть до, включая dp.

Идея состоит в том, чтобы привить dp, (требуемый узел <p>), до dd, (самый верхний узел <div>), после удаления всех других промежуточных тегов <div>. NodeSet позволяет легко unlink большого количества тегов одновременно.

Прочитайте о at, чтобы понять, почему это важно для такого рода проблем.

1 голос
/ 12 февраля 2020

Обычно я не большой поклонник изменяемых структур, которые использует Nokogiri, но в этом случае я думаю, что это работает в ваших интересах. Примерно так может работать:

def recurse node
  # depth first so we don't accidentally modify a collection while
  # we're iterating through it.
  node.elements.each do |child|
    recurse(child)
  end

  # replace this element's children with it's grandchildren
  # assuming it meets all the criteria
  if merge_candidate?(node)
    node.children = node.elements.first.children
  end
end

def merge_candidate? node, name: 'div'
  return false unless node.element?
  return false unless node.attributes.empty?
  return false unless node.name == name
  return false unless node.elements.length == 1
  return false unless node.elements.first.name == name
  return false unless node.elements.first.attributes.empty?

  true
end
[18] pry(main)> file = File.read('test.html')
[19] pry(main)> doc = Nokogiri.parse(file)
[20] pry(main)> puts doc
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  </head>
  <body>
    <div>
      <div>
        <div>
          <p>Some text</p>
        </div>  
      </div>
    </div>
  </body>
</html>
[21] pry(main)> recurse(doc)
[22] pry(main)> puts doc
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  </head>
  <body>
    <div>
      <p>Some text</p>
    </div>
  </body>
</html>
=> nil
[23] pry(main)> 

...