Phân tích cú pháp XML với không gian tên trong Python thông qua 'ElementTree'


163

Tôi có XML sau đây mà tôi muốn phân tích cú pháp bằng Python ElementTree:

<rdf:RDF xml:base="http://dbpedia.org/ontology/"
    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns:owl="http://www.w3.org/2002/07/owl#"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
    xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
    xmlns="http://dbpedia.org/ontology/">

    <owl:Class rdf:about="http://dbpedia.org/ontology/BasketballLeague">
        <rdfs:label xml:lang="en">basketball league</rdfs:label>
        <rdfs:comment xml:lang="en">
          a group of sports teams that compete against each other
          in Basketball
        </rdfs:comment>
    </owl:Class>

</rdf:RDF>

Tôi muốn tìm tất cả owl:Classcác thẻ và sau đó trích xuất giá trị của tất cả các rdfs:labeltrường hợp bên trong chúng. Tôi đang sử dụng mã sau đây:

tree = ET.parse("filename")
root = tree.getroot()
root.findall('owl:Class')

Do không gian tên, tôi nhận được lỗi sau.

SyntaxError: prefix 'owl' not found in prefix map

Tôi đã thử đọc tài liệu tại http://effbot.org/zone/element-namespaces.htmlm nhưng tôi vẫn không thể làm việc này vì XML ở trên có nhiều không gian tên lồng nhau.

Vui lòng cho tôi biết cách thay đổi mã để tìm tất cả các owl:Classthẻ.

Câu trả lời:


226

ElementTree không quá thông minh về không gian tên. Bạn cần phải cung cấp cho các .find(), findall()iterfind()các phương pháp một cuốn từ điển namespace rõ ràng. Điều này không được ghi chép lại rất tốt:

namespaces = {'owl': 'http://www.w3.org/2002/07/owl#'} # add more as needed

root.findall('owl:Class', namespaces)

Tiền tố chỉ được tra cứu trong namespacestham số bạn truyền vào. Điều này có nghĩa là bạn có thể sử dụng bất kỳ tiền tố không gian tên nào bạn muốn; API tách ra owl:một phần, tìm kiếm URL không gian tên tương ứng trong namespacestừ điển, sau đó thay đổi tìm kiếm để tìm biểu thức XPath {http://www.w3.org/2002/07/owl}Classthay thế. Tất nhiên, bạn cũng có thể sử dụng cùng một cú pháp:

root.findall('{http://www.w3.org/2002/07/owl#}Class')

Nếu bạn có thể chuyển sang lxmlthư viện thì mọi thứ sẽ tốt hơn; thư viện đó hỗ trợ API ElementTree tương tự, nhưng thu thập các không gian tên cho bạn trong một .nsmapthuộc tính trên các thành phần.


7
Cảm ơn bạn. Bất kỳ ý tưởng làm thế nào tôi có thể có được không gian tên trực tiếp từ XML, mà không cần mã hóa cứng? Hoặc làm thế nào tôi có thể bỏ qua nó? Tôi đã thử findall ('{*} Class') nhưng nó không hoạt động trong trường hợp của tôi.
Kostanos

7
Bạn sẽ phải tự quét cây cho xmlnscác thuộc tính; như đã nêu trong câu trả lời, lxmllàm điều này cho bạn, xml.etree.ElementTreemô-đun không. Nhưng nếu bạn đang cố gắng khớp một phần tử cụ thể (đã được mã hóa cứng), thì bạn cũng đang cố gắng khớp một phần tử cụ thể trong một không gian tên cụ thể. Không gian tên đó sẽ không thay đổi giữa các tài liệu nhiều hơn tên thành phần. Bạn cũng có thể mã hóa cứng với tên thành phần.
Martijn Pieters

14
@Jon: register_namespacechỉ ảnh hưởng nối tiếp, không tìm kiếm.
Martijn Pieters

5
Bổ sung nhỏ có thể hữu ích: khi sử dụng cElementTreethay vì ElementTree, findallsẽ không lấy không gian tên làm đối số từ khóa, mà chỉ đơn giản là một đối số bình thường, tức là sử dụng ctree.findall('owl:Class', namespaces).
egpbos

2
@Bludwarf: Các tài liệu có đề cập đến nó (bây giờ, nếu không phải khi bạn viết điều đó), nhưng bạn phải đọc chúng một cách cẩn thận. Xem phần Phân tích cú pháp XML với Không gian tên : có một ví dụ tương phản với việc sử dụng findallkhông có và sau đó với namespaceđối số, nhưng đối số không được đề cập như một trong các đối số của phương thức phương thức trong phần đối tượng Phần tử .
Wilson F

57

Dưới đây là cách thực hiện điều này với lxml mà không cần phải mã hóa không gian tên hoặc quét văn bản cho chúng (như Martijn Pieters đề cập):

from lxml import etree
tree = etree.parse("filename")
root = tree.getroot()
root.findall('owl:Class', root.nsmap)

CẬP NHẬT :

5 năm sau tôi vẫn gặp phải các vấn đề khác nhau. lxml giúp như tôi đã trình bày ở trên, nhưng không phải trong mọi trường hợp. Các nhà bình luận có thể có một điểm hợp lệ liên quan đến kỹ thuật này khi nói đến việc hợp nhất các tài liệu, nhưng tôi nghĩ hầu hết mọi người đều gặp khó khăn khi tìm kiếm tài liệu.

Đây là một trường hợp khác và cách tôi xử lý nó:

<?xml version="1.0" ?><Tag1 xmlns="http://www.mynamespace.com/prefix">
<Tag2>content</Tag2></Tag1>

xmlns không có tiền tố có nghĩa là các thẻ chưa được trộn có được không gian tên mặc định này. Điều này có nghĩa là khi bạn tìm kiếm Tag2, bạn cần bao gồm không gian tên để tìm nó. Tuy nhiên, lxml tạo ra một mục nsmap với Không phải là khóa và tôi không thể tìm thấy cách nào để tìm kiếm nó. Vì vậy, tôi đã tạo một từ điển không gian tên mới như thế này

namespaces = {}
# response uses a default namespace, and tags don't mention it
# create a new ns map using an identifier of our choice
for k,v in root.nsmap.iteritems():
    if not k:
        namespaces['myprefix'] = v
e = root.find('myprefix:Tag2', namespaces)

3
URL không gian tên đầy đủ mã định danh không gian tên mà bạn được cho là mã cứng. Tiền tố cục bộ ( owl) có thể thay đổi từ tệp này sang tệp khác. Vì vậy, làm những gì câu trả lời này cho thấy là một ý tưởng thực sự xấu.
Matti Virkkunen 18/03/2016

1
@MattiVirkkunen chính xác nếu định nghĩa cú có thể thay đổi từ tệp này sang tệp khác, chúng ta có nên sử dụng định nghĩa được xác định trong mỗi tệp thay vì mã hóa không?
Loïc Faure-Lacroix

@ LoïcFaure-Lacroix: Thông thường các thư viện XML sẽ cho phép bạn trừu tượng hóa phần đó. Bạn thậm chí không cần biết hoặc quan tâm đến tiền tố được sử dụng trong chính tệp, bạn chỉ cần xác định tiền tố của riêng mình cho mục đích phân tích cú pháp hoặc chỉ sử dụng tên không gian tên đầy đủ.
Matti Virkkunen

câu trả lời này đã giúp tôi ít nhất có thể sử dụng chức năng tìm kiếm. Không cần phải tạo tiền tố của riêng bạn. Tôi vừa thực hiện key = list (root.nsmap.keys ()) [0] và sau đó thêm khóa dưới dạng tiền tố: root.find (f '{key}: Tag2', root.nsmap)
Eelco van Vliet

30

Lưu ý : Đây là câu trả lời hữu ích cho thư viện chuẩn ElementTree của Python mà không sử dụng các không gian tên được mã hóa cứng.

Để trích xuất các tiền tố và URI của không gian tên từ dữ liệu XML, bạn có thể sử dụng ElementTree.iterparsehàm, chỉ phân tích cú pháp các sự kiện bắt đầu không gian tên ( start-ns ):

>>> from io import StringIO
>>> from xml.etree import ElementTree
>>> my_schema = u'''<rdf:RDF xml:base="http://dbpedia.org/ontology/"
...     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
...     xmlns:owl="http://www.w3.org/2002/07/owl#"
...     xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
...     xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
...     xmlns="http://dbpedia.org/ontology/">
... 
...     <owl:Class rdf:about="http://dbpedia.org/ontology/BasketballLeague">
...         <rdfs:label xml:lang="en">basketball league</rdfs:label>
...         <rdfs:comment xml:lang="en">
...           a group of sports teams that compete against each other
...           in Basketball
...         </rdfs:comment>
...     </owl:Class>
... 
... </rdf:RDF>'''
>>> my_namespaces = dict([
...     node for _, node in ElementTree.iterparse(
...         StringIO(my_schema), events=['start-ns']
...     )
... ])
>>> from pprint import pprint
>>> pprint(my_namespaces)
{'': 'http://dbpedia.org/ontology/',
 'owl': 'http://www.w3.org/2002/07/owl#',
 'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
 'rdfs': 'http://www.w3.org/2000/01/rdf-schema#',
 'xsd': 'http://www.w3.org/2001/XMLSchema#'}

Sau đó, từ điển có thể được chuyển làm đối số cho các chức năng tìm kiếm:

root.findall('owl:Class', my_namespaces)

1
Điều này hữu ích cho những người trong chúng ta mà không truy cập vào lxml và không muốn không gian tên mã cứng.
delrocco

1
Tôi đã nhận được lỗi: ValueError: write to closedcho dòng này filemy_namespaces = dict([node for _, node in ET.iterparse(StringIO(my_schema), events=['start-ns'])]). Có ý kiến ​​nào muốn sai không?
Yuli

Có lẽ lỗi liên quan đến lớp io.StringIO, từ chối các chuỗi ASCII. Tôi đã thử nghiệm công thức của mình với Python3. Thêm tiền tố chuỗi unicode 'u' vào chuỗi mẫu, nó cũng hoạt động với Python 2 (2.7).
Davide Brunato

Thay vì dict([...])bạn cũng có thể sử dụng hiểu chính tả.
Arminius

Thay vì StringIO(my_schema)bạn cũng có thể đặt tên tệp của tệp XML.
JustAC0der

6

Tôi đã sử dụng mã tương tự như vậy và thấy rằng nó luôn đáng để đọc tài liệu ... như thường lệ!

findall () sẽ chỉ tìm các phần tử là con trực tiếp của thẻ hiện tại . Vì vậy, không thực sự TẤT CẢ.

Có thể đáng để bạn cố gắng để mã của bạn hoạt động với các mục sau, đặc biệt là nếu bạn đang xử lý các tệp xml lớn và phức tạp để các yếu tố phụ (v.v.) cũng được bao gồm. Nếu bạn biết chính mình nơi các yếu tố trong xml của bạn, thì tôi cho rằng nó sẽ ổn! Chỉ cần nghĩ rằng điều này là đáng nhớ.

root.iter()

ref: https://docs.python.org/3/l Library / xml.etree.elementtree.html#finding-interesting-elements "Element.findall () chỉ tìm thấy các phần tử có thẻ là con trực tiếp của phần tử hiện tại. Element.find () tìm thấy đứa con đầu tiên với một thẻ cụ thể và Element.text truy cập nội dung văn bản của phần tử. Element.get () truy cập các thuộc tính của phần tử: "


6

Để có được không gian tên ở định dạng không gian tên của nó, ví dụ: {myNameSpace}bạn có thể làm như sau:

root = tree.getroot()
ns = re.match(r'{.*}', root.tag).group(0)

Bằng cách này, bạn có thể sử dụng nó sau này trong mã của mình để tìm các nút, ví dụ: sử dụng phép nội suy chuỗi (Python 3).

link = root.find(f"{ns}link")

0

Giải pháp của tôi dựa trên nhận xét của @Martijn Pieters:

register_namespace chỉ ảnh hưởng nối tiếp, không tìm kiếm.

Vì vậy, mẹo ở đây là sử dụng các từ điển khác nhau để tuần tự hóa và tìm kiếm.

namespaces = {
    '': 'http://www.example.com/default-schema',
    'spec': 'http://www.example.com/specialized-schema',
}

Bây giờ, đăng ký tất cả các không gian tên để phân tích cú pháp và viết:

for name, value in namespaces.iteritems():
    ET.register_namespace(name, value)

Để tìm kiếm ( find(), findall(), iterfind()), chúng tôi cần một tiền tố không trống. Vượt qua các chức năng này một từ điển sửa đổi (ở đây tôi sửa đổi từ điển gốc, nhưng điều này phải được thực hiện chỉ sau khi không gian tên được đăng ký).

self.namespaces['default'] = self.namespaces['']

Bây giờ, các chức năng từ find()gia đình có thể được sử dụng với defaulttiền tố:

print root.find('default:myelem', namespaces)

nhưng

tree.write(destination)

không sử dụng bất kỳ tiền tố nào cho các thành phần trong không gian tên mặc định.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.