سؤال

أحاول تحليل المحتوى في جدول بيانات OpenOffice ODS. تنسيق ODS هو في الأساس مجرد zipfile مع عدد من المستندات. يتم تخزين محتوى جدول البيانات في "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: ':'
هل كانت مفيدة؟

المحلول

لو root.nsmap يحتوي على table بادئة مساحة الاسم ، يمكنك:

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

findall(path) يقبل {namespace}name بناء الجملة بدلا من namespace:name. لذلك path يجب معالجتها مسبقًا باستخدام قاموس مساحة الاسم إلى {namespace}name تشكل قبل تمريره إلى findall().

نصائح أخرى

فيما يلي طريقة للحصول على جميع مساحات الأسماء في وثيقة XML (وافتراض عدم وجود صراع بادئة).

أستخدم هذا عند تحليل مستندات XML حيث أعرف مسبقًا ما هي عناوين URL لمادة الاسم ، والبادئة فقط.

        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)

ربما أول ما يلاحظه هو أن مساحات الأسماء محددة في مستوى العنصر, ، ليس مستوى المستند.

في أغلب الأحيان ، يتم الإعلان عن جميع مساحات الأسماء في عنصر جذر المستند (office:document-content هنا) ، مما يوفر لنا تحليل كل شيء لجمع داخلي xmlns نطاقات.

ثم يتضمن عنصر NSMAP:

  • مساحة الاسم الافتراضية مع None البادئة (ليس دائما)
  • جميع مساحات الأسماء ، ما لم يتم تجاوزها.

إذا لم يتم دعم مساحة الاسم الافتراضية ، كما تم ذكر Chrisr ، يمكنك استخدام أ فهم القولون لتصفيةها في تعبير أكثر إحكاما.

لديك بناء جملة مختلف قليلاً لـ 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)

لن يجد Etree عناصر مسجلة الأسماء إذا لم يكن هناك xmlns التعريفات في ملف XML. على سبيل المثال:

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

في بعض الأحيان هذه هي البيانات التي يتم تقديمها. لذا ، ماذا يمكنك أن تفعل عندما لا توجد مساحة اسم؟

الحل الخاص بي: أضف واحد.

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')
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top