Написание XML с пространством имен с помощью REXML

Я работаю с XML-кодом, который включает в себя несколько пространств имен (в частности, ResourceSync, который включает теги с пространством имен). в документах Sitemap).

Когда я создаю элементы REXML, я могу установить глобальное пространство имен:

foo = REXML::Element.new('foo')
foo.add_namespace('http://foo.com/')

puts foo # outputs <foo xmlns='http://foo.com/'/>

и я могу создать пространство имен с префиксом:

foo.add_namespace('bar', 'http://bar.org/')

puts foo # outputs <foo xmlns:bar='http://bar.org/' xmlns='http://foo.com/'/>

Однако если я затем добавлю еще один элемент с тем же URI пространства имен, что и префикс, но без явного использования префикса --

bar = REXML::Element.new('bar')
bar.add_namespace('http://bar.org/')
foo.add_element(bar)

-- REXML недостаточно умен, чтобы заметить наличие префикса и использовать его. Вместо ожидаемого

<foo xmlns:bar='http://bar.org/' xmlns='http://foo.com/'>
  <bar:bar/>
</foo>

Я получаю излишне многословное:

<foo xmlns:bar='http://bar.org/' xmlns='http://foo.com/'>
  <bar xmlns='http://bar.org/'/>
</foo>

Я мог бы обойти это, полностью игнорируя URI пространства имен и просто взломав префикс в имени элемента:

baz = REXML::Element.new('bar:baz')
foo.add_element(baz)

Однако во время создания элемента единственное, что я знаю наверняка, это URI пространства имен — я не знаю, к какому родительскому элементу он будет добавлен или какие префиксы пространства имен могут там существовать. (И в любом случае префиксы пространств имен на самом деле не являются частью логической модели документа, в отличие от URI пространств имен.)

Есть ли способ заставить REXML разрешать префиксы во время вывода и/или простой способ постобработки документа REXML для использования префиксов?

Обратите внимание, что я не ищу, например. решение Nokogiri, так как я использую библиотеку xml-mapping, которая использует REXML для внутреннего использования (похоже, у него также нет концепции пространств имен, но я нашел способ обойти это).


person David Moles    schedule 18.05.2015    source источник


Ответы (1)


Попробуйте этот код:

require 'rexml/document'

foo = REXML::Element.new('foo')
foo.add_namespace('http://foo.com/')
foo.add_namespace('bar', 'http://bar.org/')

bar = REXML::Element.new('bar')
bar.add_namespace('http://bar.org/')
foo.add_element(bar)

def normalize_namespace!(elem)
  if elem.attributes['xmlns']
    prefix = elem.namespaces.reject { |key, _| key == 'xmlns' }.key(elem.namespace)
    elem.name = "#{prefix}:#{elem.name}"
    elem.delete_namespace
  end
end

foo.root.each_element_with_attribute('xmlns') { |e| normalize_namespace!(e) }

puts foo
# => <foo xmlns:bar='http://bar.org/' xmlns='http://foo.com/'><bar:bar/></foo>

Вот пояснения:

  1. each_element_with_attribute просматривает все узлы xml с атрибутом xmlns.
  2. namespaces возвращает хэш со всеми пространствами имен. для этого узла, включая его предков, например, для bar это будет: {"xmlns"=>"http://foo.com/", "bar"=>"http://bar.org/"}
  3. namespace возвращает наиболее подходящее пространство имен для узла, изучение его атрибутов и предков. Для bar возвращается http://bar.org/.
  4. Метод доступа name= назначает как короткое, так и расширенное имя (последний будет использоваться при рендеринге, если он существует)
  5. Наконец, delete_namespace удаляет лишние xmlns='http://bar.org/' на bar.
person Alexey Shein    schedule 25.09.2015