2016-04-04 3 views
3

Ich möchte diese Datei öffnen und alle Elemente abrufen, die mit us-gaap beginnen.So fügen Sie der vorhandenen XML-Datei einen Namespace hinzu

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

Um Elemente zu bekommen ich so versucht:

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 kehrt {}, so dass ich glaube, ich Namespace us-gaap hinzuzufügen haben.

Es gibt einige Fragen zum "Namespace mit Nokogiri hinzufügen", aber es sieht so aus, als ob man ein neues XML-Dokument erstellt und nicht, wie man einen Namespace zu existierenden Dokumenten hinzufügt.

Wie kann ich einem vorhandenen Dokument einen Namespace hinzufügen?

Ich weiß, dass ich den Namespace durch Nokogiri::XML::Document#remove_namespaces! entfernen kann, aber ich möchte es nicht verwenden, weil es auch notwendige Informationen entfernt.

+1

Statt einer Verbindung, die minimalen Daten extrahieren, das Problem zu demonstrieren und setzen es in deine Frage. Welchen Code haben Sie geschrieben, um dieses Problem zu lösen? Bitte lesen Sie "[mcve]". Wir benötigen die minimale Eingabe und ein Beispiel für die gewünschte Ausgabe. –

+0

Danke für die Warnung, ich füge ein minimales Beispiel hinzu. – ironsand

Antwort

3

Sie haben eine XY Problem gefragt. Sie denken, dass das Problem ist, dass Sie einen fehlenden Namespace hinzufügen müssen; Das eigentliche Problem ist, dass die Datei, die Sie analysieren möchten, keine gültige XML-Datei ist.

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

Zum Beispiel kann das <ACCEPTANCE-DATETIME> ‚Element‘ geöffnet auf der Linie 3 ist nie geschlossen, und auf der Linie 16 gibt es einen rohen Ampersand im Text:
STANDARD INDUSTRIAL CLASSIFICATION: ELECTRIC HOUSEWARES & FANS [3634]
die sollte als Einheit entkommen sein.

Allerdings hat das Dokument gültige XML-Fragmente innerhalb es! Insbesondere gibt es ein XML-Dokument, das den Namespace xmlns:us-gaap aus den Zeilen 27243-49312 definiert. Lassen Sie uns nur das herausfinden, indem wir nur das Wissen verwenden, dass das Wurzelelement den gewünschten Namespace definiert, und die Annahmen, dass kein Element mit demselben Namen innerhalb des Dokuments verschachtelt ist und dass das Wurzelelement kein unescaped > Zeichen enthält irgendein Attribut. (Diese Annahmen sind für diese Datei gültig, kann aber nicht für jede XML-Datei gültig.)

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 

Der obige Code behandelt den Fall, in dem es mehr als ein XML-Dokument in der txt-Datei sein, obwohl in dieser Fall, es gibt nur einen.

Decoded, die gaap_finder regex sagt dies:

  • %r{...}m - dies ist ein regulärer Ausdruck (das die Schrägstriche darin, unescaped) mit "mehrzeiligen Modus", in dem eine Periode Zeilenumbrüche übereinstimmen
  • (...) - erfaßt alles, was wir finden
  • < - beginnen mit einem wörtlichen „weniger als“ Symbol
  • (\w+) - findet ein oder mehr Wortzeichen (den Tag nennen), und speichern Sie sie
  • - die Wortzeichen muss ein Leerzeichen gefolgt werden (wichtig, die <xsd:xbrl ...> Element in dieser Datei erfassen zu vermeiden)
  • [^>]+ - gefolgt von einem oder mehreren Zeichen, die nicht ein „Größer ist als“Symbol (um sicherzustellen, dass wir in dem gleichen Elemente bleiben, die uns in gestartet)
  • xmlns:us-gaap\s*= - gefolgt von dieser wörtlichen Namespace-Deklaration (das Leerzeichen hast trennen sie von dem Gleichheitszeichen)
  • .+? - gefolgt von irgendetwas (so wenig wie möglich) ...
  • </\2> - ... bis Sie einen End-Tag mit dem gleichen Namen wie das, was wir für den Namen des Start-Tages erfaßt

Aufgrund der Art und Weise scan funktioniert, wenn die Regex Gruppen hat die Erfassung, jedes Ergebnis ist ein Array mit zwei Elementen, wobei das erste Element das gesamte erfasste XML und das zweite Element der Name des Tags ist, das wir erfasst haben (was wir "verwerfen", indem wir es der Variablen _ zuweisen).


Wenn Sie über Ihr Capturing weniger Magie sein, wird das Text-Dateiformat immer jedes XML-Dokument in <XBRL>...</XBRL> zu wickeln. Also, Sie dies tun könnten jede XML-Datei zu verarbeiten (es gibt sieben, fünf davon passiert keinen us-gaap Namensraum haben):

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

Ich konnte nicht herausfinden, wie man ein vorhandenes Dokument mit einem neuen Namespace aktualisiert, aber da Nokogiri Namespaces auf dem Wurzelelement erkennt, und diese Namespaces syntaktisch nur Attribute sind, können Sie das Dokument mit einem neuen aktualisieren Namespace-Deklaration, serialisiert das Dokument in einen String und Wieder analysieren es:

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">]>] 
+0

Dies ist ein interessanter Hack für ein gültiges XML-Dokument (das das OP als Beispielcode enthielt). Das Originaldokument ist jedoch kein gültiges XML-Dokument, das löst also nicht das reale Problem. Ich habe dich zum ersten Mal aufgezogen, aber du hast dich für den zweiten verwarnt, also ist das Nettoergebnis keine Veränderung. :/ – Phrogz

+0

Die vom OP gestellte Frage lautete: "Wie kann ich einem vorhandenen Dokument einen Namensraum hinzufügen?". Er gab an, dass die Nokogiri-XPath-Abfrage ein leeres Ergebnis ergab, was bedeutet, dass er bereits die gewünschten Informationen als XML-Dokument analysieren konnte. Also habe ich diese Frage beantwortet. Also im Grunde schlagen Sie vor, dass Sie mich aufgrund Ihrer eigenen Annahme, dass das OP sein Problem nicht richtig beschrieben hat, abmelden würden. Das ist dein Vorrecht, aber ich denke, es ist falsch. – Dathan

+0

Sie erhalten einen Zuschlag für Höflichkeit und Vernunft und für die Beantwortung der Frage in den Titel. Aber ich glaube nicht, dass meine Annahme ist. Schauen Sie sich die ersten beiden Zeilen der Frage an. Öffnen Sie dann das Dokument. Dann versuche, damit zu arbeiten. Wie in meiner Antwort angemerkt, werden Sie feststellen, dass es nicht funktioniert, weil es kein XML-Dokument ist, und das Analysieren als solches ist ein verschwommener Fehler.Der einzige Grund dafür ist das irreführende Beispiel, weil TheTinMan das OP gebeten hat, eins hinzuzufügen, und das OP dann Code hinzugefügt hat, der leider nicht mit dem wirklichen Problem übereinstimmt. – Phrogz