如何将命名空间添加到现有 xml 文件
How to add a namespace to existing xml file
我想打开这个文件并获取所有以 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
returns {}
, 所以我想我必须添加命名空间 us-gaap
.
有一些关于 "adding namespace with Nokogiri" 的问题,但看起来是关于如何创建新的 XML 文档,而不是如何向现有文档添加命名空间。
如何向现有文档添加命名空间?
我知道我可以通过 Nokogiri::XML::Document#remove_namespaces!
删除命名空间,但我不想使用它,因为它也会删除必要的信息。
我不知道如何使用新的命名空间更新现有文档,但由于 Nokogiri 会识别根元素上的命名空间,而这些命名空间在语法上只是属性,您可以使用新的命名空间声明,将文档序列化为字符串,并重新解析它:
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">]>]
您问了 XY Problem。您认为问题是您需要添加缺少的名称space;真正的问题是您尝试解析的文件无效 XML.
require 'nokogiri'
doc = Nokogiri.XML( IO.read('0001558370-15-001143.txt') )
doc.errors.length
#=> 5716
例如,第3行打开的<ACCEPTANCE-DATETIME>
'element'永远不会关闭,而第16行文本中有一个原始的符号:
STANDARD INDUSTRIAL CLASSIFICATION: ELECTRIC HOUSEWARES & FANS [3634]
应该作为一个实体转义。
但是,该文件在内有有效的XML片段!特别是,有一个 XML 文档从第 27243-49312 行定义了 xmlns:us-gaap
namespace。让我们提取一下,仅使用根元素定义我们想要的名称 space 的知识,以及文档中没有嵌套同名元素的 假设 ,并且根元素在任何属性中都没有未转义的 >
字符。 (这些假设适用于此文件,但可能不适用于每个 XML 文件。)
txt = IO.read('0001558370-15-001143.txt')
gaap_finder = %r{(<(\w+) [^>]+xmlns:us-gaap=.+?</>)}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
— 这是一个带有 "multiline mode" 的正则表达式(允许斜线,未转义),其中句点将匹配换行符
(...)
— 捕获我们发现的一切
<
— 以文字 "less-than" 符号开头
(\w+)
— 找到一个或多个单词字符(标签名称),并保存它们
</code> — 单词字符后面必须跟一个 space(重要的是避免捕获此文件中的 <code><xsd:xbrl ...>
元素)
[^>]+
— 后跟一个或多个不是 "greater-than" 符号的字符(以确保我们停留在开始时的同一元素中)
xmlns:us-gaap\s*=
— 后跟此文字名称space 声明(可以用白色space 将其与等号分隔开)
.+?
— 然后是任何东西(尽可能少)...
</>
— ...直到您看到一个与我们捕获的起始标签同名的结束标签
由于正则表达式具有捕获组时 scan
的工作方式,每个结果都是一个双元素数组,其中第一个元素是整个捕获 XML,第二个元素是我们捕获的标签的名称(我们 "discard" 通过将其分配给 _
变量)。
如果您想减少捕获的魔力,文本文件格式似乎总是将每个 XML 文档包装在 <XBRL>...</XBRL>
中。因此,您可以这样做来处理每个 XML 文件(共有七个,其中五个恰好没有任何 us-gaap
名称space):
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)
我想打开这个文件并获取所有以 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
returns {}
, 所以我想我必须添加命名空间 us-gaap
.
有一些关于 "adding namespace with Nokogiri" 的问题,但看起来是关于如何创建新的 XML 文档,而不是如何向现有文档添加命名空间。
如何向现有文档添加命名空间?
我知道我可以通过 Nokogiri::XML::Document#remove_namespaces!
删除命名空间,但我不想使用它,因为它也会删除必要的信息。
我不知道如何使用新的命名空间更新现有文档,但由于 Nokogiri 会识别根元素上的命名空间,而这些命名空间在语法上只是属性,您可以使用新的命名空间声明,将文档序列化为字符串,并重新解析它:
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">]>]
您问了 XY Problem。您认为问题是您需要添加缺少的名称space;真正的问题是您尝试解析的文件无效 XML.
require 'nokogiri'
doc = Nokogiri.XML( IO.read('0001558370-15-001143.txt') )
doc.errors.length
#=> 5716
例如,第3行打开的<ACCEPTANCE-DATETIME>
'element'永远不会关闭,而第16行文本中有一个原始的符号:
STANDARD INDUSTRIAL CLASSIFICATION: ELECTRIC HOUSEWARES & FANS [3634]
应该作为一个实体转义。
但是,该文件在内有有效的XML片段!特别是,有一个 XML 文档从第 27243-49312 行定义了 xmlns:us-gaap
namespace。让我们提取一下,仅使用根元素定义我们想要的名称 space 的知识,以及文档中没有嵌套同名元素的 假设 ,并且根元素在任何属性中都没有未转义的 >
字符。 (这些假设适用于此文件,但可能不适用于每个 XML 文件。)
txt = IO.read('0001558370-15-001143.txt')
gaap_finder = %r{(<(\w+) [^>]+xmlns:us-gaap=.+?</>)}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
— 这是一个带有 "multiline mode" 的正则表达式(允许斜线,未转义),其中句点将匹配换行符(...)
— 捕获我们发现的一切<
— 以文字 "less-than" 符号开头(\w+)
— 找到一个或多个单词字符(标签名称),并保存它们</code> — 单词字符后面必须跟一个 space(重要的是避免捕获此文件中的 <code><xsd:xbrl ...>
元素)[^>]+
— 后跟一个或多个不是 "greater-than" 符号的字符(以确保我们停留在开始时的同一元素中)xmlns:us-gaap\s*=
— 后跟此文字名称space 声明(可以用白色space 将其与等号分隔开).+?
— 然后是任何东西(尽可能少)...</>
— ...直到您看到一个与我们捕获的起始标签同名的结束标签
由于正则表达式具有捕获组时 scan
的工作方式,每个结果都是一个双元素数组,其中第一个元素是整个捕获 XML,第二个元素是我们捕获的标签的名称(我们 "discard" 通过将其分配给 _
变量)。
如果您想减少捕获的魔力,文本文件格式似乎总是将每个 XML 文档包装在 <XBRL>...</XBRL>
中。因此,您可以这样做来处理每个 XML 文件(共有七个,其中五个恰好没有任何 us-gaap
名称space):
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)