2013-06-06 2 views
1

У меня есть код, который выглядит как:Как создать уникальный массив с помощью Nokogiri?

file = Nokogiri::XML(File.open('file.xml')) 
test = file.xpath("//title") #all <title> elements in xml file 

Тогда, когда я пытаюсь:

puts test.uniq 

Я получаю следующее сообщение об ошибке:

undefined method `uniq' for #<Nokogiri::XML::NodeSet:0x000000011b8bf8> 

Is test не массив? Если это не так, как я могу сделать это?

В противном случае, как получить только уникальные значения из массива test?

ответ

7

Is test not an array? If it's not, how do I make it one?

test будет NodeSet:

Nokogiri::XML('<xml><foo/></xml>').xpath('//foo').class 
=> Nokogiri::XML::NodeSet 

foo = Nokogiri::XML('<xml><foo/></xml>').xpath('//foo') 
=> [#<Nokogiri::XML::Element:0x8109a674 name="foo">] 

foo.is_a? Array 
=> false 

foo.is_a? Enumerable 
=> true 

Чтобы включить его в арру у использовать to_a:

foo.respond_to? :to_a 
=> true 

Однако, в этом нет необходимости, потому что он также реагирует на map, each, и все нормальные вещи, которые мы ожидали бы при переборе массива, так как она включает в себя Enumerable. map, по определению, автоматически возвращает массив, поэтому в ваших комментариях и вопросе вас интересует конверсия.

foo.methods.sort - Object.methods 
=> [:%, :&, :+, :-, :/, :<<, :[], :add_class, :after, :all?, :any?, :at, :at_css, :at_xpath, :attr, :attribute, :before, :children, :chunk, :collect, :collect_concat, :count, :css, :cycle, :delete, :detect, :document, :document=, :drop, :drop_while, :each, :each_cons, :each_entry, :each_slice, :each_with_index, :each_with_object, :empty?, :entries, :filter, :find, :find_all, :find_index, :first, :flat_map, :grep, :group_by, :index, :inject, :inner_html, :inner_text, :last, :length, :map, :max, :max_by, :member?, :min, :min_by, :minmax, :minmax_by, :none?, :one?, :partition, :pop, :push, :reduce, :reject, :remove, :remove_attr, :remove_class, :reverse, :reverse_each, :search, :select, :set, :shift, :size, :slice, :slice_before, :sort, :sort_by, :take, :take_while, :text, :to_a, :to_ary, :to_html, :to_xhtml, :to_xml, :unlink, :wrap, :xpath, :zip, :|] 

Я подозреваю, что причина uniq не реализована на это очень трудно понять, как проверить на уникальность. Очень простой тег, например:

<div class="foo" id="bar"> 

функционально такие же, как:

<div id="bar" class="foo"> 

но очевидные to_s тестов потерпят неудачу, потому что они не будут соответствовать тесту строки равенства.

Теги должны быть нормированы на лету, чтобы поместить их параметры в том же порядке, а затем преобразуются в строки, но что, если параметр class был "foo1 foo2" в первом теге и "foo2 foo1" во второй? Код uniq должен окунуться в определенные параметры и изменить порядок? И, что, если тег является контейнером, например div? Должны ли дети узла также рассматриваться в тесте uniq?

Я думаю, что это банда червей, большая часть из нас быстро отступит от нее, и те, кто попытается определить uniq, узнают очень ценный урок о кроличьих дырах. Вместо этого вы можете определить uniq, как это подходит вашему конкретному приложению, поэтому оно имеет смысл для вас. Я думаю, это отличное дизайнерское решение для авторов Нокогири.

+0

Удивленный это не принятый ответ. – Phrogz

+1

Эх, я был просто «splainin», как ловить рыбу, чтобы сделать себя счастливым, поэтому меня это не беспокоит. Вы знаете, как это происходит, некоторые люди берут первую рыбу вместо того, чтобы ждать, смогут ли они поймать что-то лучше. –

+0

Я не видел этого ответа раньше, это многое прояснило для меня. Вместо этого я принял это как ответ. Спасибо Tin Man! – Kush

1

пожалуйста, попробуйте -

puts test.map(&:text).uniq 

См один пример кода, чтобы продемонстрировать, как это работает:

require "nokogiri" 

doc = Nokogiri::HTML(<<-EOF) 
<a class = "foo" href = "https://example.com"> Click here </a> 
EOF 

node = 2.times.map{|n| n = Nokogiri::XML::Node.new('title', doc); n.content = "xxx";n } 
node # => [#<Nokogiri::XML::Element:0x4637712 name="title" children=[#<Nokogiri::XML::Text:0x4636efc "xxx">]>, #<Nokogiri::XML::Element:0x4637690 name="title" children=[#<Nokogiri::XML::Text:0x4636218 "xxx">]>] 


nodeset = Nokogiri::XML::NodeSet.new(doc,node) 
nodeset # => [#<Nokogiri::XML::Element:0x4637712 name="title" children=[#<Nokogiri::XML::Text:0x4636efc "xxx">]>, #<Nokogiri::XML::Element:0x4637690 name="title" children=[#<Nokogiri::XML::Text:0x4636218 "xxx">]>] 

nodeset.map{|i| i.text }.uniq # => ["xxx"] 
+0

Кажется не работает, у меня все еще есть несколько ' xxx' то же самое. – Kush

+0

@ Куш вижу мое редактирование. –

+0

Отлично! Оно работает! Если бы вы могли объяснить (в ответе), что такое 'map (&: text)', я выберу его в качестве правильного ответа. Благодаря! – Kush