2009-03-05 8 views
4

Ich habe eine XML-Datei in einer flachen Struktur. Wir kontrollieren nicht das Format dieser XML-Datei, sondern müssen nur damit umgehen. Ich habe die Felder umbenannt, weil sie stark domänenspezifisch sind und für das Problem keinen Unterschied machen.Navigieren zu Knoten mit Xpath in flacher Struktur

<attribute name="Title">Book A</attribute> 
<attribute name="Code">1</attribute> 
<attribute name="Author"> 
    <value>James Berry</value> 
    <value>John Smith</value> 
</attribute> 
<attribute name="Title">Book B</attribute> 
<attribute name="Code">2</attribute> 
<attribute name="Title">Book C</attribute> 
<attribute name="Code">3</attribute> 
<attribute name="Author"> 
    <value>James Berry</value> 
</attribute> 

Wichtige Dinge zu beachten: Die Datei ist nicht besonders hierarchisch. Bücher werden durch das Auftreten eines Attributelements mit name = 'Title' begrenzt. Der Name = 'Author'-Attributknoten ist jedoch optional.

Gibt es eine einfache Xpath-Anweisung, die ich verwenden kann, um die Autoren von Buch 'n' zu finden? Es ist leicht, den Titel des Buches 'n' zu identifizieren, aber der Wert des Autoren ist optional. Und Sie können nicht einfach den folgenden Autor nehmen, denn im Fall von Buch 2 würde dies den Autor für Buch 3 geben.

Ich habe eine Zustandsmaschine geschrieben, um dies als eine Reihe von Elementen zu analysieren, aber ich kann ' t helfen zu denken, dass es eine Möglichkeit gegeben hätte, die gewünschten Ergebnisse direkt zu erzielen.

Antwort

3

Wir wollen das „Attribut“ Element der @name ‚Autor‘, die ein „Attribut“ Element der @name ‚Titel‘ folgt mit einem Wert von 'Buch n', ohne irgendein anderes "attribute" Element von @name 'Titel' zwischen ihnen (denn wenn es welche gibt, dann hat der Autor ein anderes Buch geschrieben).

Said anders, es bedeutet, dass wir wollen, einen Autor, dessen erste vorhergehenden Titel (die es „gehört“) ist derjenige, wir suchen:

//attribute[@name='Author'] 
[preceding-sibling::attribute[@name='Title'][1][contains(.,'Book N')]] 

N = C => findet <attribute name="Author"><value>James Berry</value></attribute>

N = B => findet nichts

mit den Tasten und/oder Gruppierungsfunktionen zur Verfügung in XSLT 2.0 würde dies einfacher machen (und viel schneller, wenn die Datei ist groß).

(SO Code Parser scheint es '//' steht für 'Kommentare', aber in XPath nicht zu denken !!! Seufz.)

+0

Funktioniert in Notepad ++ XML-Tools. –

0

Ich bin mir nicht sicher, ob Sie wirklich dorthin gehen wollen: Das Einfachste, was ich gefunden habe, war, vom Autor zu gehen, den vorherigen Titel zu bekommen und dann zu überprüfen, ob der erste Autor oder Titelfolger tatsächlich ein Titel war. Hässlich!

/books/attribute[@name="Author"] 
    [preceding-sibling::attribute[@name="Title" and string()="Book B"] 
           [following-sibling::attribute[ @name="Author" 
                  or @name="Title" 
                  ] 
           [1] 
           [@name="Author"] 
           ] 
    ][1] 

(Ich habe die Bücher Tag rund um die Datei zu wickeln).

Ich testete das mit libxml2 BTW, mit xml_grep2, aber nur auf die Beispieldaten, die Sie gaben, so sind weitere Tests willkommen).

+0

Ich habe das Gefühl, dass mein Ansatz, die Daten nodeweise zu analysieren, für dieses verrückte Layout tatsächlich besser ist als der Versuch, die Daten mit xpath zu extrahieren. Das reale Beispiel hat ungefähr 10 verschiedene optionale Elemente –

+0

Der Ansatz, den ich verwenden würde, wäre, das XML zuerst in etwas nützliches vorzuverarbeiten. Verwenden Sie entweder den Namen als Tag-Name und umschließen Sie alle Daten für ein Buch in einem _book_ -Tag, oder senden Sie einfach alles in eine DB und behandeln Sie Daten, nicht irgendwelche verrückten Datenströme! – mirod

0

alle Titel auswählen und Vorlage

<xsl:template match="/"> 
    <xsl:apply-templates select="//attribute[@name='Title']"/> 
</xsl:template> 

Im Template Ausgabe Titel, überprüfen dann, wenn nächsten Titel vorhanden sind. Falls nicht, folgenden Autor ausgeben. Wenn es vorhanden ist, überprüfen Sie, ob der folgende Autorknoten des folgenden Buchs mit dem folgenden Autorknoten des aktuellen Buchs übereinstimmt. Wenn es ist, bedeutet es, dass die aktuellen Buch kein Autor hat:

<xsl:template match="*"> 
    <book> 
    <title><xsl:value-of select="."/></title> 
    <author> 
    <xsl:if test="not(following::attribute[@name='Title']) or following::attribute[@name='Author'] != following::attribute[@name='Title']/following::attribute[@name='Author']"> 
    <xsl:value-of select="following::attribute[@name='Author']"/> 
    </xsl:if> 
    </author> 
    </book> 
</xsl:template> 
2

Nun, ich Elementtree verwenden Daten aus dem obigen XML zu extrahieren. Ich habe dieses XML in der Datei namens foo gespeichert.xml

from xml.etree.ElementTree import fromstring 

def extract_data(): 
    """Returns list of dict of book and 
    its authors.""" 

    f = open('foo.xml', 'r+') 
    xml = f.read() 
    elem = fromstring(xml) 
    attribute_list = elem.findall('attribute') 
    dic = {} 
    lst = [] 

    for attribute in attribute_list: 
     if attribute.attrib['name'] == 'Title': 
      key = attribute.text 
     if attribute.attrib['name'] == 'Author': 
      for v in attribute.findall('value'): 
       lst.append(v.text) 
      value = lst 
      lst = [] 
      dic[key] = value 
    return dic 

Wenn Sie diese Funktion aufrufen, erscheint diese:

{'Book A': ['James Berry', 'John Smith'], 'Book C': ['James Berry']} 

Ich hoffe, das ist das, was Sie suchen. Wenn nicht, dann gib einfach ein bisschen mehr an. :)

+0

Hmm ... diese Antwort verwendet XPath überhaupt nicht! Nicht was der OP wollte. –

+0

Richtig! Ich habe erwähnt, was genau ich verwendet habe, um das zu lösen. Jedenfalls werde ich versuchen, dies auch mit XPATH zu lösen. :) – aatifh

1

Als bambax in seiner Antwort erwähnt, eine Lösung XSLT-Taste ist effiziente, vor allem für große XML-Dokumente:

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
<xsl:output omit-xml-declaration="yes"/> 
<!--           --> 
<xsl:key name="kAuthByTitle" 
    match="attribute[@name='Author']" 
    use="preceding-sibling::attribute[@name='Title'][1]"/> 
<!--           --> 
    <xsl:template match="/"> 
     Book C Author: 
     <xsl:copy-of select= 
     "key('kAuthByTitle', 'Book C')"/> 
    <!--           --> 
     ==================== 
     Book B Author: 
     <xsl:copy-of select= 
     "key('kAuthByTitle', 'Book B')"/> 
    </xsl:template> 
</xsl:stylesheet> 

Wenn die obige Transformation auf diesem XML-Dokument angewandt wird:

<t> 
    <attribute name="Title">Book A</attribute> 
    <attribute name="Code">1</attribute> 
    <attribute name="Author"> 
     <value>James Berry</value> 
     <value>John Smith</value> 
    </attribute> 
    <attribute name="Title">Book B</attribute> 
    <attribute name="Code">2</attribute> 
    <attribute name="Title">Book C</attribute> 
    <attribute name="Code">3</attribute> 
    <attribute name="Author"> 
     <value>James Berry</value> 
    </attribute> 
</t> 

die korrekte Ausgabe erzeugt wird:

Book C Author: 
    <attribute name="Author"> 
    <value>James Berry</value> 
</attribute> 

    ==================== 
    Book B Author: 

Beachten Sie, dass die Verwendung der XPath-Abkürzung "//" so weit wie möglich vermieden werden sollte, da dies normalerweise dazu führt, dass das gesamte XML-Dokument bei jeder Auswertung des XPath-Ausdrucks gescannt wird.