Создание XML-документа с корневым элементом с пространством имен с помощью Nokogiri builder

Я реализую экспортер для формата данных XML, для которого требуются пространства имен. Я использую Nokogiri XML Builder (версия 1.4.0) для этого.

Однако я не могу заставить Nokogiri создать root node с пространством имен.

Это работает:

Nokogiri::XML::Builder.new { |xml| xml.root('xmlns:foobar' => 'my-ns-url') }.to_xml

<?xml version="1.0"?>
<root xmlns:foobar="my-ns-url"/>

Как это делает:

Nokogiri::XML::Builder.new do |xml| 
  xml.root('xmlns:foobar' => 'my-ns-url') { xml['foobar'].child }
end.to_xml

<?xml version="1.0"?>
<root xmlns:foobar="my-ns-url">
  <foobar:child/>
</root>

Однако мне нужно что-то вроде <foo:root>, и это не работает:

Nokogiri::XML::Builder.new { |xml| xml['foobar'].root('xmlns:foobar' => 'my-ns-url') }.to_xml

NoMethodError: undefined method `namespace_definitions' for #<Nokogiri::XML::Document:0x11bfef8 name="document">

Пространства имен должны быть определены перед использованием, по-видимому, поэтому нет возможности добавить его в корень node.

Я нашел вопрос об этом в списке рассылки Nokogiri, но у него не было ответов. У кого-нибудь есть решение?

Ответ 1

require 'rubygems'
require 'nokogiri'

puts Nokogiri::XML::Builder.new { |xml| 
  xml.root("xmlns:foo"=>"url") {
    xml.parent.namespace = xml.parent.namespace_definitions.find{|ns|ns.prefix=="foo"}
    xml['foo'].child
  }
}.to_xml

Вы не можете использовать xml['foo'] до того, как будет определено пространство имен, то есть перед тем, как передать его в качестве аргумента в корневой каталог node. Таким образом, приведенный выше код добавляет пространство имен после факта к корню node.

Ответ 2

Два года спустя я нашел более чистый способ сделать это, используя ссылку на Nokogiri::XML::Builder для получения корня node и добавить к нему пространство имен. Как и предыдущее решение, для этого требуется, чтобы root node существовал до того, как пространства имён могут быть добавлены к нему.

Я изменил элемент <root> на <rootElement>, чтобы это было более ясно:

builder = Nokogiri::XML::Builder.new do |xml| 
  xml.rootElement do
    # create a namespace and save it for later
    ns = xml.doc.root.add_namespace_definition('foo', 'my-ns-url')
    # you can also create multiple name spaces
    xml.doc.root.add_namespace_definition('bar', 'http://example.com/bar')

    # assign the saved namespace to rootElement
    xml.doc.root.namespace = ns

    xml['foo'].child
    xml['bar'].child
  end
end

Теперь builder.to_xml вернется:

<?xml version="1.0"?>
<foo:rootElement xmlns:foo="my-ns-url" xmlns:bar="http://example.com/bar">
  <foo:child/>
  <bar:child/>
</foo:rootElement>

Мне нравится это лучше, потому что вам не нужно искать пространство имен, и проще добавить несколько пространств имен.

Ответ 3

Спустя три года после ответа Люка это стало еще проще. Теперь вы можете использовать пространство имен "до", которое оно определило. Этот код:

require 'nokogiri'

NAMESPACES = { 'xmlns:foo' => 'bar', 'xmlns:baz' => 'bat' }

builder = Nokogiri::XML::Builder.new { |xml|
  xml['foo'].RootElement(NAMESPACES) {
    xml['baz'].FirstChild
  }
}

puts builder.to_xml

Выводит этот XML:

<?xml version="1.0"?>
<foo:RootElement xmlns:foo="bar" xmlns:baz="bat">
  <baz:FirstChild/>
</foo:RootElement>