web-dev-qa-db-ja.com

lxmlのfind / findallでxml名前空間を使用するにはどうすればよいですか?

OpenOfficeODSスプレッドシートのコンテンツを解析しようとしています。 ods形式は基本的に、多数のドキュメントを含む単なるzipファイルです。スプレッドシートのコンテンツは「content.xml」に保存されます。

import zipfile
from lxml import etree

zf = zipfile.ZipFile('spreadsheet.ods')
root = etree.parse(zf.open('content.xml'))

スプレッドシートの内容はセルにあります:

table = root.find('.//{urn:oasis:names:tc:opendocument:xmlns:table:1.0}table')

行をまっすぐ進むこともできます。

rows = root.findall('.//{urn:oasis:names:tc:opendocument:xmlns:table:1.0}table-row')

個々の要素は名前空間について知っています。

>>> table.nsmap['table']
'urn:oasis:names:tc:opendocument:xmlns:table:1.0'

Find/findallで名前空間を直接使用するにはどうすればよいですか?

明らかな解決策は機能しません。

テーブルから行を取得しようとしています:

>>> root.findall('.//table:table')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "lxml.etree.pyx", line 1792, in lxml.etree._ElementTree.findall (src/lxml/lxml.etree.c:41770)
  File "lxml.etree.pyx", line 1297, in lxml.etree._Element.findall (src/lxml/lxml.etree.c:37027)
  File "/usr/lib/python2.6/dist-packages/lxml/_elementpath.py", line 225, in findall
    return list(iterfind(elem, path))
  File "/usr/lib/python2.6/dist-packages/lxml/_elementpath.py", line 200, in iterfind
    selector = _build_path_iterator(path)
  File "/usr/lib/python2.6/dist-packages/lxml/_elementpath.py", line 184, in _build_path_iterator
    selector.append(ops[token[0]](_next, token))
KeyError: ':'
27
saffsd

_root.nsmap_にtable名前空間プレフィックスが含まれている場合は、次のことができます。

_root.xpath('.//table:table', namespaces=root.nsmap)
_

findall(path)は、_{namespace}name_の代わりに_namespace:name_構文を受け入れます。したがって、pathは、findall()に渡す前に、名前空間ディクショナリを使用して_{namespace}name_形式に前処理する必要があります。

20
jfs

XMLドキュメント内のすべての名前空間を取得する方法は次のとおりです(プレフィックスの競合がないと仮定します)。

これは、名前空間のURLが何であるかを事前に知っていて、プレフィックスのみがわかっているXMLドキュメントを解析するときに使用します。

        doc = etree.XML(XML_string)

        # Getting all the name spaces.
        nsmap = {}
        for ns in doc.xpath('//namespace::*'):
            if ns[0]: # Removes the None namespace, neither needed nor supported.
                nsmap[ns[0]] = ns[1]
        doc.xpath('//prefix:element', namespaces=nsmap)
11
ChrisR

おそらく最初に気付くのは、名前空間がドキュメントレベル。ではなく要素レベルで定義されていることです。

ただし、ほとんどの場合、すべての名前空間はドキュメントのルート要素(office:document-content here)、これにより、内部のxmlnsスコープを収集するためにすべてを解析する必要がなくなります。

次に、要素nsmapには次のものが含まれます。

  • None接頭辞付きのデフォルト名前空間(常にではない)
  • オーバーライドされない限り、すべての祖先名前空間。

ChrisRが述べたように、デフォルトの名前空間がサポートされていない場合は、 dict comprehension を使用して、よりコンパクトな式でフィルターで除外できます。

Xpathと ElementPath の構文は少し異なります。


したがって、最初のテーブルのすべての行を取得するために使用できるコードは次のとおりです(テスト:lxml=3.4.2):

import zipfile
from lxml import etree

# Open and parse the document
zf = zipfile.ZipFile('spreadsheet.ods')
tree = etree.parse(zf.open('content.xml'))

# Get the root element
root = tree.getroot()

# get its namespace map, excluding default namespace
nsmap = {k:v for k,v in root.nsmap.iteritems() if k}

# use defined prefixes to access elements
table = tree.find('.//table:table', nsmap)
rows = table.findall('table:table-row', nsmap)

# or, if xpath is needed:
table = tree.xpath('//table:table', namespaces=nsmap)[0]
rows = table.xpath('table:table-row', namespaces=nsmap)
8
RockyRoad

XMLファイルにxmlns定義がない場合、Etreeは名前空間要素を検出しません。例えば:

import lxml.etree as etree

xml_doc = '<ns:root><ns:child></ns:child></ns:root>'

tree = etree.fromstring(xml_doc)

# finds nothing:
tree.find('.//ns:root', {'ns': 'foo'})
tree.find('.//{foo}root', {'ns': 'foo'})
tree.find('.//ns:root')
tree.find('.//ns:root')

時々それはあなたが与えられるデータです。では、名前空間がない場合はどうすればよいでしょうか。

私の解決策:1つ追加してください。

import lxml.etree as etree

xml_doc = '<ns:root><ns:child></ns:child></ns:root>'
xml_doc_with_ns = '<ROOT xmlns:ns="foo">%s</ROOT>' % xml_doc

tree = etree.fromstring(xml_doc_with_ns)

# finds what you're looking for:
tree.find('.//{foo}root')
1
dsummersl