使用REXML编写命名空间XML

时间:2015-05-18 23:07:30

标签: ruby xml-namespaces 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解决方案,因为我正在使用一个内部使用REXML的库xml-mapping(因为它似乎也没有任何命名空间概念,但我找到了一种解决方法这一点)。

1 个答案:

答案 0 :(得分:0)

试试这段代码:

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遍历所有属性为xmlns的xml节点。
  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