Domanda

Ho un file xml in una struttura piatta. Non controlliamo il formato di questo file xml, dobbiamo solo occuparcene. Ho rinominato i campi perché sono altamente specifici del dominio e non fanno davvero alcuna differenza al problema.

<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>

Aspetti chiave da notare: il file non è particolarmente gerarchico. I libri sono delimitati da un'occorrenza di un elemento di attributo con nome = 'Titolo'. Ma il nodo dell'attributo name = 'Author' è facoltativo.

Esiste una semplice istruzione xpath che posso usare per trovare gli autori del libro 'n'? È facile identificare il titolo del libro 'n', ma il valore degli autori è facoltativo. E non puoi semplicemente prendere il seguente autore perché nel caso del libro 2, questo darebbe all'autore per il libro 3.

Ho scritto una macchina a stati per analizzarlo come una serie di elementi, ma non posso fare a meno di pensare che ci sarebbe stato un modo per ottenere direttamente i risultati desiderati.

È stato utile?

Soluzione

Vogliamo l'attributo " " elemento di @name 'Autore' che sta seguendo un " attributo " elemento di @name 'Title' con un valore di 'Book n', senza altri " attributo " elemento di @name 'Title' tra loro (perché se ci sono, allora l'autore ha creato un altro libro).

Detto diversamente, significa che vogliamo un autore di cui il primo titolo precedente (quello a cui " appartiene a ") è quello che stiamo cercando:

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

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

N = B = > non trova nulla

L'uso dei tasti e / o delle funzioni di raggruppamento disponibili in XSLT 2.0 renderebbe questo più semplice (e molto più veloce se il file è grande).

(il parser del codice SO sembra pensare che "//" sta per "commenti" ma in XPath non lo è !!! Sospiro.)

Altri suggerimenti

Bene, ho usato Elementtree per estrarre i dati dall'XML sopra. Ho salvato questo XML nel file denominato foo.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

Quando esegui questa funzione otterrai questo:

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

Spero che questo sia quello che stai cercando. In caso contrario, basta specificare un po 'di più. :)

Come bambax ha notato nella sua risposta, una soluzione che utilizza chiavi XSLT è più efficiente , specialmente per documenti XML di grandi dimensioni:

<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>

Quando la trasformazione sopra è applicata su questo documento XML:

<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>

viene prodotto l'output corretto:

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

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

Si noti che l'uso dell'abbreviazione "//" XPath dovrebbe essere evitato il più possibile , poiché di solito provoca la scansione dell'intero documento XML su ogni valutazione dell'espressione XPath.

Seleziona tutti i titoli e applica il modello

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

Nel titolo di output del modello, controlla se esiste il titolo successivo. In caso contrario, output dopo l'autore. Se esiste, controlla se il seguente nodo autore del seguente libro è uguale al seguente nodo autore del libro corrente. Se lo è, significa che il libro attuale non ha autore:

<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>

Non sono sicuro che tu voglia davvero andarci: il più semplice che ho trovato è stato di andare dall'autore, ottenere il titolo precedente, quindi controllare che il primo autore o il titolo seguente fosse effettivamente un titolo. Brutto!

/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]

(ho aggiunto il tag libri per avvolgere il file).

L'ho provato con libxml2 BTW, usando xml_grep2 , ma solo sui dati di esempio che hai fornito, quindi ulteriori test sono benvenuti).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top