سؤال

ما هي أذكى طريقة لجعل Nokogiri تحديد كل المحتوى بين البداية والعنصر الإيقاف (بما في ذلك start-element)؟

تحقق من رمز المثال أدناه لفهم ما أبحث عنه:

require 'rubygems'
require 'nokogiri'

value = Nokogiri::HTML.parse(<<-HTML_END)
  "<html>
    <body>
      <p id='para-1'>A</p>
      <div class='block' id='X1'>
        <p class="this">Foo</p>
        <p id='para-2'>B</p>
      </div>
      <p id='para-3'>C</p>
      <p class="that">Bar</p>
      <p id='para-4'>D</p>
      <p id='para-5'>E</p>
      <div class='block' id='X2'>
        <p id='para-6'>F</p>
      </div>
      <p id='para-7'>F</p>
      <p id='para-8'>G</p>
    </body>
  </html>"
HTML_END

parent = value.css('body').first

# START element
@start_element = parent.at('p#para-3')
# STOP element
@end_element = parent.at('p#para-7')

يجب أن تبدو النتيجة (قيمة الإرجاع) هكذا:

<p id='para-3'>C</p>
<p class="that">Bar</p>
<p id='para-4'>D</p>
<p id='para-5'>E</p>
<div class='block' id='X2'>
  <p id='para-6'>F</p>
</div>
<p id='para-7'>F</p>

تحديث: هذا هو الحل الحالي ، على الرغم من أنني أعتقد أنه يجب أن يكون هناك شيء أكثر ذكاءً:

@my_content = ""
@selected_node = true

def collect_content(_start)

  if _start == @end_element
    @my_content << _start.to_html
    @selected_node = false
  end

  if @selected_node == true
    @my_content << _start.to_html
    collect_content(_start.next)
  end

end

collect_content(@start_element)

puts @my_content
هل كانت مفيدة؟

المحلول

oneliner-smart-too-smart الذي يستخدم العودية:

def collect_between(first, last)
  first == last ? [first] : [first, *collect_between(first.next, last)]
end

حل تكراري:

def collect_between(first, last)
  result = [first]
  until first == last
    first = first.next
    result << first
  end
  result
end

تحرير: (قصير) شرح لـ Asterix

يسمى مشغل splat. إنها "unrolls" صفيف:

array = [3, 2, 1]
[4, array]  # => [4, [3, 2, 1]]
[4, *array] # => [4, 3, 2, 1]

some_method(array)  # => some_method([3, 2, 1])
some_method(*array) # => some_method(3, 2, 1)

def other_method(*array); array; end
other_method(1, 2, 3) # => [1, 2, 3] 

نصائح أخرى

# monkeypatches for Nokogiri::NodeSet
# note: versions of these functions will be in Nokogiri 1.3
class Nokogiri::XML::NodeSet
  unless method_defined?(:index)
    def index(node)
      each_with_index { |member, j| return j if member == node }
    end
  end

  unless method_defined?(:slice)
    def slice(start, length)
      new_set = Nokogiri::XML::NodeSet.new(self.document)
      length.times { |offset| new_set << self[start + offset] }
      new_set
    end
  end
end

#
#  solution #1: picking elements out of node children
#  NOTE that this will also include whitespacy text nodes between the <p> elements.
#
possible_matches = parent.children
start_index = possible_matches.index(@start_element)
stop_index = possible_matches.index(@end_element)
answer_1 = possible_matches.slice(start_index, stop_index - start_index + 1)

#
#  solution #2: picking elements out of a NodeSet
#  this will only include elements, not text nodes.
#
possible_matches = value.xpath("//body/*")
start_index = possible_matches.index(@start_element)
stop_index = possible_matches.index(@end_element)
answer_2 = possible_matches.slice(start_index, stop_index - start_index + 1)

من أجل الاكتمال أ xpath فقط المحلول :)
إنه يبني تقاطعًا لمجموعتين ، الأشقاء التاليين لعنصر البدء والأخوة السابقة للعنصر النهائي.

في الأساس يمكنك بناء تقاطع مع:

  $a[count(.|$b) = count($b)]

قليلا مقسمة على المتغيرات لقدرة على القراءة:

@start_element = "//p[@id='para-3']"
@end_element = "//p[@id='para-7']"
@set_a = "#@start_element/following-sibling::*"
@set_b = "#@end_element/preceding-sibling::*"

@my_content = value.xpath("#@set_a[ count(.|#@set_b) = count(#@set_b) ]
                         | #@start_element | #@end_element")

لا يتضمن الأشقاء العنصر نفسه ، لذلك يجب تضمين عناصر البداية والنهاية في التعبير بشكل منفصل.

يحرر: حل أسهل:

@start_element = "p[@id='para-3']"
@end_element = "p[@id='para-7']"
@my_content = value.xpath("//*[preceding-sibling::#@start_element and
                               following-sibling::#@end_element]
                         | //#@start_element | //#@end_element")
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top