Question

J'ai un fichier XML dans une structure plate. Nous ne contrôlons pas le format de ce fichier XML, nous devons simplement nous en occuper. J'ai renommé les champs car ils sont très spécifiques à un domaine et ne font pas vraiment la différence.

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

Points essentiels à noter: le fichier n’est pas particulièrement hiérarchique. Les livres sont délimités par l'occurrence d'un élément d'attribut avec name = 'Title'. Mais le noeud attribut name = 'Author' est facultatif.

Existe-t-il une simple déclaration xpath que je peux utiliser pour rechercher les auteurs du livre 'n'? Il est facile d'identifier le titre du livre 'n', mais la valeur de l'auteur est optionnelle. Et vous ne pouvez pas prendre simplement l'auteur suivant car dans le cas du livre 2, cela donnerait à l'auteur le livre 3.

J'ai écrit une machine à états pour analyser cela comme une série d'éléments, mais je ne peux pas m'empêcher de penser qu'il y aurait eu un moyen d'obtenir directement les résultats que je veux.

Était-ce utile?

La solution

Nous voulons l'attribut " " élément de @nom 'Auteur' qui suit un " attribut " élément de @name 'Title' avec la valeur 'Book n', sans autre attribut " " élément de @name 'Title' entre eux (car s'il y en a, alors l'auteur est l'auteur d'un autre livre).

Dit différemment, cela signifie que nous voulons un auteur dont le premier titre précédent (celui auquel & "appartient" à & ";") est celui que nous recherchons:

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

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

N = B = > ne trouve rien

L'utilisation de touches et / ou de fonctions de regroupement disponibles dans XSLT 2.0 faciliterait cette tâche (et beaucoup plus rapidement si le fichier est volumineux).

(L'analyseur de code SO semble penser que "//" signifie "commentaires", mais ce n'est pas le cas dans XPath !!! Soupir.)

Autres conseils

J'ai utilisé Elementtree pour extraire des données du code XML ci-dessus. J'ai enregistré ce fichier XML dans un fichier nommé 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

Lorsque vous exécuterez cette fonction, vous obtiendrez ceci:

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

J'espère que c'est ce que vous recherchez. Sinon, spécifiez un peu plus. :)

Comme le note Bambax dans sa réponse, une solution utilisant des clés XSLT est plus efficace , en particulier pour les documents XML volumineux:

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

Lorsque la transformation ci-dessus est appliquée à ce document 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>

la sortie correcte est produite:

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

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

Notez que l’abréviation "//" de XPath doit être évitée autant que possible , car elle entraîne généralement l’analyse de l’ensemble du document XML à chaque évaluation de l’expression XPath.

Sélectionnez tous les titres et appliquez le modèle

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

Dans le titre de sortie du modèle, vérifiez si le titre suivant existe. Sinon, indiquez le nom de l'auteur suivant. S'il existe, vérifiez si le nœud auteur suivant du livre suivant est identique au nœud auteur suivant du livre actuel. Si tel est le cas, cela signifie que le livre en cours n’a pas d’auteur:

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

Je ne suis pas sûr que vous vouliez vraiment y aller: le plus simple que j'ai trouvé était d'aller à l'auteur, obtenir le titre précédent, puis de vérifier que le premier auteur ou le titre suivant était bien un titre. Moche!

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

(J'ai ajouté la balise books pour envelopper le fichier).

J'ai testé cela avec libxml2 BTW, en utilisant xml_grep2 , mais uniquement sur les exemples de données que vous avez fournis. plus de tests sont les bienvenus).

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top