如何将命名空间添加到现有xml文件

时间:2016-04-04 08:08:02

标签: ruby nokogiri

我想打开此文件并获取所有以us-gaap开头的元素。

ftp://ftp.sec.gov/edgar/data/916789/0001558370-15-001143.txt

要获得我尝试过的元素:

str = '<html><body><us-gaap:foo>foo</us-gaap:foo></body></html>'
doc = Nokogiri::XML(File.read(str))
doc.xpath('//us-gaap:*')
Nokogiri::XML::XPath::SyntaxError: Undefined namespace prefix: //us-gaap:*
from /Users/ironsand/.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/nokogiri-1.6.7.2/lib/nokogiri/xml/searchable.rb:165:in `evaluate'

doc.namespaces返回{},因此我认为我必须添加名称空间us-gaap

有一些关于“使用Nokogiri添加命名空间”的问题,但它看起来像是如何创建新的XML文档,而不是如何将命名空间添加到现有文档。

如何在现有文档中添加命名空间?

我知道我可以通过Nokogiri::XML::Document#remove_namespaces!删除命名空间,但我不想使用它,因为它也删除了必要的信息。

2 个答案:

答案 0 :(得分:3)

你问过XY Problem。您认为问题在于您需要添加缺少的命名空间;真正的问题是您尝试解析的文件不是有效的XML。

require 'nokogiri'
doc = Nokogiri.XML( IO.read('0001558370-15-001143.txt') )
doc.errors.length
#=> 5716

例如,<ACCEPTANCE-DATETIME>&#39;元素&#39;在第3行打开从未关闭,在第16行,文本中有一个原始的&符号:
STANDARD INDUSTRIAL CLASSIFICATION: ELECTRIC HOUSEWARES & FANS [3634]
应该作为一个实体逃脱。

但是,该文档在中有有效的XML片段!特别是,有一个XML文档定义了xmlns:us-gaap命名空间,来自第27243-49312行。让我们只使用根元素定义我们想要的命名空间的知识,以及假设,即没有任何具有相同名称的元素嵌套在文档中,并且根目录element在任何属性中都没有未转义的>字符。 (这些假设对此文件有效,但可能对每个XML文件都无效。)

txt = IO.read('0001558370-15-001143.txt')
gaap_finder = %r{(<(\w+) [^>]+xmlns:us-gaap=.+?</\2>)}m
txt.scan(gaap_finder) do |xml,_|
  doc = Nokogiri.XML( xml )
  gaaps = doc.xpath('//us-gaap:*')
  p gaaps.length
  #=> 569
end

上面的代码处理txt文件中可能有多个XML文档的情况,但在这种情况下只有一个。

解码后,gaap_finder正则表达式说明了这一点:

  • %r{...}m - 这是一个正则表达式(允许使用斜杠,未转义),使用&#34;多行模式&#34;,其中句号将与换行符匹配
  • (...) - 抓住我们发现的一切
  • < - 以文字&#34;小于&#34;开头符号
  • (\w+) - 找到一个或多个单词字符(标记名称),然后保存它们
  • - 单词字符后面必须跟一个空格(对于避免捕获此文件中的<xsd:xbrl ...>元素很重要)
  • [^>]+ - 后跟一个或多个不是&#34;大于&#34;的字符。符号(以确保我们保持与我们开始时相同的元素)
  • xmlns:us-gaap\s*= - 后跟此文字命名空间声明(可能有空格将其与等号分隔)
  • .+? - 后跟任何事情(尽可能少)......
  • </\2> - 直到您看到一个与我们为起始标记名称捕获的名称相同的结束标记

由于正则表达式具有捕获组时scan的工作方式,每个结果都是一个双元素数组,其中第一个元素是整个捕获的XML,第二个元素是我们标记的名称捕获(我们通过将其分配给_变量来放弃&#34;丢弃&#34;

如果你想减少捕获的魔力,文本文件格式似乎始终将每个XML文档包装在<XBRL>...</XBRL>中。所以,你可以这样做来处理每个XML文件(有七个,其中五个没有任何us-gaap名称空间):

txt   = IO.read('0001558370-15-001143.txt')
xbrls = %r{(?<=<XBRL>).+?(?=</XBRL>)}m      # find text inside <XBRL>…</XBRL>
txt.scan(xbrls) do |xml|
  doc = Nokogiri.XML( xml )
  if doc.namespaces["xmlns:us-gaap"]
    gaaps = doc.xpath('//us-gaap:*')
    p gaaps.length
  end
end
#=> 569
#=> 0        (for the XML Schema document that defines the namespace)

答案 1 :(得分:2)

我无法弄清楚如何使用新的命名空间更新现有文档,但由于Nokogiri将识别根元素上的命名空间,并且这些命名空间在语法上只是属性,因此您可以使用新的名称空间声明,将doc序列化为字符串,然后重新解析它:

str = '<html><body><us-gaap:foo>foo</us-gaap:foo></body></html>'
doc_without_ns = Nokogiri::XML(str)
doc_without_ns.root['xmlns:us-gaap'] = 'http://your/actual/ns/here'
doc = Nokogiri::XML(doc_without_ns.to_xml)
doc.xpath("//us-gaap:*")
# Returns [#<Nokogiri::XML::Element:0x3ff375583f9c name="foo" namespace=#<Nokogiri::XML::Namespace:0x3ff375583f24 prefix="us-gaap" href="http://your/actual/ns/here"> children=[#<Nokogiri::XML::Text:0x3ff375583768 "foo">]>]