Parsowanie XML'a

<parametry>
  <parametr>
    <tytul>ABC</tytul>
    <przeznaczenie>Szkolenie</przeznaczenie>
    <autor>Kowalski</autor>
  </parametr>
  <parametr>
    <tytul>XYZ</tytul>
    <przeznaczenie>Szkolenie</przeznaczenie>
    <autor>Kowalski</autor>
  </parametr>
  <parametr>
    <tytul>ABC</tytul>
    <przeznaczenie>Szkolenie</przeznaczenie>
    <autor>Nowakowski</autor>
  </parametr>
<parametry>

Cześć,
Podpowiedzcie proszę, jak wyciągnąć z tego unikalne tytuły jako string?
czyli w efekcie chciałbym dostać: “ABC, XYZ”?


Unikalność wyciągniesz sobie w Rubym, tak czy inaczej musisz całego xmla najpierw sparsować i przekonwertować do struktury Rubiego.

…Parsuję, ale miałem nadzieję, że jest jakieś magiczne :wink: xpath("./*[local-name()='parametr']")… które da mi takie unikalne wartości.
Nie ma? Trudno. Będę iterował, wrzucał do tablicy i później uniq zastosuję.

A to nie lepiej do hasha?

require 'nokogiri'

doc = Nokogiri::XML(File.open("data.xml"))
titles_xml = doc.xpath("//tytul")

titles = titles_xml.map(&:text).uniq

p titles

Nie do końca, ale natchnąłeś mnie fragmentem .map(&:text).uniq
:slight_smile:

Dzięki!! :slight_smile: :slight_smile: :slight_smile:

gwoli ścisłości - nie zawsze. To prawda, jeśli chodzi o parsery, które budują “drzewo” z parsowanego dokumentu. Ale jeśli parsowany dokument jest duży, to czasem warto skorzystać z tzw. “streaming parsers”, które emitują “zdarzenia” odpowiadające odwiedzanym elementom dokumentu - taki trochę Visitor Pattern.

Komplikacją jest to, że trzeba samu zarządzać “kontekstem” (w tym przypadku: jeśli dostaniemy powiadomienie, że odwiedzamy własnie element “tytul”, to musimy w tym momencie moc stwierdzic, ze biezacym rodzicem obecnego elementu jest “parametr”, czyli musielismy juz wczesniej rowniez obsluzyc wizyte elementu “parametr” - ale tylko tych 2ch w tym konkretnym przypadku). I bedzie to dzialac szybko nawet dla baaardzo duzych dokumentow (a nawet pewnie dla nieskonczonego strumienia XML-a) :slight_smile:

Przykład gemu który daje możliwość takiego parsowania:


http://www.ohler.com/ox/

Przy czym Ox jest na tyle wygodny, że wystawia również interfejs nie-streamowy.

Trochę historycznego kontekstu z zamierzchlych czasów perla :slight_smile:
http://mkweb.bcgsc.ca/intranet/perlbook/pxml/ch03_02.htm
http://mkweb.bcgsc.ca/intranet/perlbook/pxml/ch03_03.htm

To może, jak kolega wyżej radził, strumieniem? Nokogiri może również tak (przydaje się dla dużych plików):

require 'set'
require 'nokogiri'

titles = Set.new

Nokogiri::XML::Reader(File.open("data.xml")).each do |node|
	if node.name == 'parametr' && node.node_type == Nokogiri::XML::Reader::TYPE_ELEMENT
		title = Nokogiri::XML(node.inner_xml).at('/tytul').text
		titles.add title unless titles.include? title
	end
end

puts titles.to_a.join(",")

Dzięki, dzięki… :slight_smile:

Ponieważ muszę przetwarzać całego XML’a (ładować wszystko do bazy), więc

my_model.create(
  ...,
  pole_tabeli: jakies_zagnieżdzienie_noda.xpath("./*[local-name()='parametr']").xpath("./*[local-name()='tytul']").map(&:text).uniq.join(", "),
  ...
)

… “daje rade” :slight_smile: :wink: