Переход к узлам с использованием xpath в плоской структуре - PullRequest
4 голосов
/ 05 марта 2009

У меня есть XML-файл в плоской структуре. Мы не контролируем формат этого xml-файла, просто имеем дело с ним. Я переименовал поля, потому что они сильно зависят от предметной области и не имеют никакого значения для проблемы.

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

Ключевые моменты, на которые следует обратить внимание: файл не является особенно иерархическим. Книги ограничены вхождением элемента атрибута с именем = 'Заголовок'. Но узел атрибута name = 'Author' является необязательным.

Есть ли простое утверждение xpath, которое я могу использовать, чтобы найти авторов книги 'n'? Легко определить название книги 'n', но значение авторов является необязательным. И вы не можете просто взять следующего автора, потому что в случае с книгой 2 это даст автора для книги 3.

Я написал конечный автомат, чтобы разобрать это как последовательность элементов, но я не могу не думать, что был бы способ напрямую получить желаемые результаты.

Ответы [ 5 ]

3 голосов
/ 05 марта 2009

Мы хотим, чтобы элемент "attribute" @name 'Author' следовал за элементом "attribute" из @name 'Title' со значением 'Book n', без каких-либо других элементов "attribute" из @name ' Заголовок «между ними (потому что, если есть, то автор является автором какой-то другой книги).

Иначе говоря, это означает, что мы хотим, чтобы автор, которого first предшествующий заголовок (тот, которому он "принадлежит"), является тем, которого мы ищем:

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

N = C => находит <attribute name="Author"><value>James Berry</value></attribute>

N = B => ничего не находит

Использование ключей и / или группирующих функций, доступных в XSLT 2.0, упростит эту задачу (и намного быстрее, если файл большой).

(парсер кода SO, кажется, думает, что «//» означает «комментарии», но в XPath это не так! Sigh.)

2 голосов
/ 05 марта 2009

Ну, я использовал Elementtree для извлечения данных из приведенного выше XML. Я сохранил этот XML в файле с именем 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

Когда вы запустите эту функцию, вы получите:

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

Надеюсь, это то, что вы ищете. Если нет, то просто укажите немного больше. :)

1 голос
/ 05 марта 2009

Как отметил bambax в своем ответе, решение с использованием ключей XSLT более эффективно , особенно для больших документов 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>

Когда вышеупомянутое преобразование применено к этому документу 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>

выводится правильный вывод:

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

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

Обратите внимание, что следует максимально избегать использования аббревиатуры * XPath "//" , поскольку это обычно приводит к сканированию всего документа XML при каждой оценке выражения XPath.

0 голосов
/ 05 марта 2009

Выберите все заголовки и примените шаблон

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

В выходном заголовке шаблона проверьте, существует ли следующий заголовок. Если нет, выведите следующего автора. Если он существует, проверьте, совпадает ли следующий узел автора следующей книги со следующим узлом автора текущей книги. Если это так, это означает, что у текущей книги нет автора:

<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>
0 голосов
/ 05 марта 2009

Я не уверен, что вы действительно хотите туда пойти: самое простое, что я нашел, это пойти от автора, получить предыдущий заголовок, а затем убедиться, что первый автор или следующий заголовок действительно были заголовком. Гадкий!

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

(я добавил тег books , чтобы обернуть файл).

Я проверил это с помощью libxml2 BTW, используя xml_grep2 , но только на приведенных вами образцах данных, поэтому приветствуются дополнительные тесты).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...