Pergunta

Eu tenho um arquivo xml em uma estrutura plana. Nós não controlamos o formato deste arquivo xml, apenas tem que lidar com isso. Eu renomeado os campos, porque eles são altamente específicos de domínio e realmente não faz qualquer diferença para o 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>

coisas importantes a serem observados: o arquivo não é particularmente hierárquica. Livros são delimitados por uma ocorrência de um elemento de atributo com nome = 'Título'. Mas a name = nó 'Autor' atributo é opcional.

Existe uma declaração XPath simples que posso usar para encontrar os autores do livro 'n'? É fácil identificar o título do livro 'n', mas o valor autores é opcional. E você não pode simplesmente tomar o seguinte autor porque no caso do livro 2, este daria o autor para o livro 3.

Eu escrevi uma máquina de estado para analisar isso como uma série de elementos, mas não posso deixar de pensar que teria sido uma maneira de obter diretamente os resultados que eu quero.

Foi útil?

Solução

Queremos que o elemento "atributo" de @name 'Autor' que está seguindo um elemento "atributo" de @name 'Título' com um valor de 'Livro n', sem qualquer outro elemento "atributo" de @ nome ' título' entre eles (porque se houver, então o autor autoria algum outro livro).

Dito de forma diferente, isso significa que queremos um autor do qual o início anterior título (aquele que "pertence") é o que estamos procurando:

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

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

N = B => não encontra nada

Usando as chaves e / ou agrupamento de funções disponíveis no XSLT 2.0 faria isso mais fácil (e muito mais rápido se o arquivo é grande).

(analisador de código SO parece pensar '//' significa 'comentários', mas em XPath não é !!! Sigh.)

Outras dicas

Bem, eu usei ElementTree para extrair dados do XML acima. Tenho guardado esse XML no arquivo chamado 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

Ao executar esta função, você receberá esta:

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

Espero que isso é o que você está procurando. Se não, então basta especificar um pouco mais. :)

Como bambax observou em sua resposta, uma solução usando chaves XSLT é mais eficiente , especialmente para grandes documentos XML:

<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 a transformação acima é aplicado sobre este 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>

a saída correta é produzido:

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

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

Note que usando o XPath abreviatura "//" deve ser evitado tanto quanto possível , como geralmente faz com que todo o documento XML a ser digitalizado em cada avaliação da expressão XPath.

Selecionar todos os títulos e aplicar modelo

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

No título de saída do modelo, verifique se próximo exist título. Se não, a saída seguinte autor. Se existir, verifique se o seguinte nó autor do livro seguinte é o mesmo que seguinte nó autor do livro atual. Se for, isso significa que o livro atual não tem autor:

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

Eu não tenho certeza que você realmente quer ir para lá: o mais simples eu encontrei foi para ir do autor, obter o título anterior, em seguida, verificar que o primeiro autor ou título seguinte era de fato um título. ! Feio

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

(eu adicionei o livros tag para envolver o arquivo).

Eu testei isso com libxml2 BTW, usando xml_grep2 , mas apenas sobre os dados de exemplo que você deu, então mais testes são bem-vindos).

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top