Упорядочивание элементов с помощью REXML XPath

Я хотел бы перебрать все элементы <HeadA> и <HeadB> в файле XML и добавить к каждому уникальный идентификатор. Подход, который я пробовал до сих пор, таков:

@xml.each_element('//HeadA | //HeadB') do |heading|
  #add a new id
end

Проблема в том, что набор узлов из XPath //HeadA | //HeadB состоит из всех HeadA, за которыми следуют все HeadB. Мне нужен упорядоченный список всех HeadA и HeadB в том порядке, в котором они появляются в документе.

Просто чтобы уточнить, мой XML может выглядеть так:

<Doc>
  <HeadA>First HeadA</HeadA>
  <HeadB>First HeadB</HeadB>
  <HeadA>Second HeadA</HeadA>
  <HeadB>Second HeadB</HeadB>
</Doc>

И что я получаю от XPath:

  <HeadA>First HeadA</HeadA>
  <HeadA>Second HeadA</HeadA>
  <HeadB>First HeadB</HeadB>
  <HeadB>Second HeadB</HeadB>

когда мне нужно получить узлы в порядке:

  <HeadA>First HeadA</HeadA>
  <HeadB>First HeadB</HeadB>
  <HeadA>Second HeadA</HeadA>
  <HeadB>Second HeadB</HeadB>

поэтому я могу добавлять идентификаторы последовательно.


person Skilldrick    schedule 15.11.2010    source источник
comment
Любой совместимый механизм XPath должен выбирать узлы в порядке документа. Ваш явно не соответствует требованиям. Настоятельно рекомендуем не его использовать и не ошибочно полагать, что это XPath.   -  person Dimitre Novatchev    schedule 16.11.2010
comment
@Dimitre Спасибо, это приятно знать.   -  person Skilldrick    schedule 16.11.2010
comment
@Dimitre: На самом деле нет спецификации, определяющей порядок результирующего набора узлов. Это ответственность за язык хостинга. Вы правы в том, что почти каждый движок XPath будет использовать порядок документов.   -  person    schedule 16.11.2010


Ответы (4)


Хорошо, 2-я попытка, но я думаю, что на этот раз у меня получилось :P

@xml.each_element('//*[self::HeadA or self::HeadB]') do |heading|
  puts heading.text
end
person D-D-Doug    schedule 15.11.2010
comment
Это сделало это! Мне удалось превратить мои старые 8 корявых линий в 5 красивых и четких линий. Спасибо :) - person Skilldrick; 15.11.2010

Использование Nokogiri для разбора XML:

xml = %q{
<Doc>
    <HeadA>First HeadA</HeadA>
    <HeadB>First HeadB</HeadB>
    <HeadA>Second HeadA</HeadA>
    <HeadB>Second HeadB</HeadB>
</Doc>
}

doc = Nokogiri::XML(xml)
doc.search('//HeadA | //HeadB').map{ |n| n.inner_text } #=> ["First HeadA", "First HeadB", "Second HeadA", "Second HeadB"]

Для вашей задачи вы можете заменить map на each или each_with_index и почти все готово. Просто добавьте код, чтобы вставить уникальный идентификатор.

person the Tin Man    schedule 15.11.2010
comment
Спасибо. Раньше я не использовал нокогири, но это похоже на хорошую рубинскую технику. - person Skilldrick; 15.11.2010
comment
Nokogiri хорош для парсинга XML и HTML. Что особенно здорово, так это то, что вы можете использовать более простые средства доступа CSS для большого количества поисковых запросов XML. - person the Tin Man; 15.11.2010

Сработает ли это для вас, если вы пройдёте через все HeadA и внутри каждого HeadA пройдёте через каждый HeadB?

@xml.each_element("//HeadA") do |headA|
  #do stuff to headA
  headA.each_element("HeadB") do |headB|
    #do stuff to headB
  end
end
person D-D-Doug    schedule 15.11.2010
comment
Нет, они не вложенные. Спасибо хоть. - person Skilldrick; 15.11.2010

Я придумал быстрое и грязное решение:

as_string = @xml.to_s
counter = 0
as_string.gsub!(/(<HeadA>|<HeadB>)/) do |str|
  result = str.sub '>', " id='#{counter}'>"
  counter += 1
  result
end
@xml = REXML::Document.new as_string

Вероятно, он не самый красивый и не самый эффективный, но он делает то, что я хотел.

Редактировать: По совету D-D-Doug я получил следующее:

counter = 0
@xml.each_element('//[self::HeadA or self::HeadB]') do |heading|
  heading.attributes['id'] = "id%03d" % counter
  counter += 1
end

что НАМНОГО приятнее.

person Skilldrick    schedule 15.11.2010