كيف يمكنني استخدام مساحات أسماء XML مع Find/Findall في LXML؟
-
25-09-2019 - |
سؤال
أحاول تحليل المحتوى في جدول بيانات 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')