2013-02-07 31 views
14

Đang cố gắng để phân tích cú pháp XML, với ElementTree, chứa thực thể không xác định (tức là  ) đặt ra:Parse XML với (X) các thực thể HTML

ParseError: undefined entity  

Trong Python 2.x thực thể XML dict có thể được cập nhật bởi tạo trình phân tích cú pháp (documentation):

parser = ET.XMLParser() 
parser.entity["nbsp"] = unichr(160) 

nhưng cách thực hiện tương tự với Python 3.x?


Cập nhật: Có được hiểu lầm từ bên cạnh tôi, vì tôi bỏ qua mà tôi đã được gọi parser.parser.UseForeignDTD(1) trước khi cố gắng để cập nhật XML thực thể dict, nhưng lại gây lỗi với phân tích cú pháp. May mắn thay, @ m.brindley đã kiên nhẫn và chỉ ra rằng thực thể XML dict vẫn tồn tại trong Python 3.x và có thể được cập nhật theo cách tương tự như trong Python 2.x

Trả lời

17

Vấn đề ở đây là các thực thể ghi nhớ hợp lệ duy nhất trong XML là quot, amp, apos, ltgt. Điều này có nghĩa là hầu hết tất cả các thực thể có tên (X) HTML phải được định nghĩa trong DTD bằng cách sử dụng entity declaration markup được định nghĩa trong XML 1.1 spec. Nếu tài liệu là để được độc lập, điều này sẽ được thực hiện với một DTD inline như vậy:

<?xml version="1.1" ?> 
<!DOCTYPE naughtyxml [ 
    <!ENTITY nbsp "&#0160;"> 
    <!ENTITY copy "&#0169;"> 
]> 
<data> 
    <country name="Liechtenstein"> 
     <rank>1&nbsp;&gt;</rank> 
     <year>2008&copy;</year> 
     <gdppc>141100</gdppc> 
     <neighbor name="Austria" direction="E"/> 
     <neighbor name="Switzerland" direction="W"/> 
    </country> 
</data> 

Các XMLParser trong xml.etree.ElementTree sử dụng một xml.parsers.expat để làm phân tích thực tế. Trong các đối số init cho XMLParser, có một khoảng trống cho 'predefined HTML entities' nhưng đối số đó chưa được triển khai. Một dict trống có tên là entity được tạo trong phương thức init và đây là những gì được sử dụng để tìm kiếm các thực thể không xác định.

Tôi không nghĩ rằng người nước ngoài (bằng cách mở rộng, ET XMLParser) có thể xử lý việc chuyển đổi các không gian tên thành XHMTL để giải quyết vấn đề này. Có thể bởi vì nó sẽ không tìm nạp các định nghĩa vùng tên bên ngoài (tôi đã thử tạo một không gian tên mặc định cho phần tử dữ liệu mặc định là xmlns="http://www.w3.org/1999/xhtml" nhưng nó không phát độc đáo) nhưng tôi không thể xác nhận điều đó. Theo mặc định, người nước ngoài sẽ tăng lỗi đối với các thực thể không phải XML nhưng bạn có thể thực hiện điều đó bằng cách định nghĩa DOCTYPE bên ngoài - điều này làm cho trình phân tích cú pháp người nước ngoài chuyển các mục nhập chưa xác định trở lại phương thức _default() của ET.XMLParser.

Phương pháp _default() sẽ tra cứu entity dict trong trường hợp XMLParser và nếu nó tìm thấy khóa khớp, nó sẽ thay thế thực thể bằng giá trị được liên kết. Điều này duy trì cú pháp Python-2.x được đề cập trong câu hỏi.

Giải pháp:

  • Nếu dữ liệu không có một DOCTYPE bên ngoài và có (X) HTML thực thể ghi nhớ, bạn đang trên may mắn. Nó không phải là hợp lệ XML và người nước ngoài là quyền ném một lỗi. Bạn nên thêm DOCTYPE bên ngoài.
  • Nếu dữ liệu có DOCTYPE bên ngoài, bạn chỉ có thể sử dụng cú pháp cũ của bạn để ánh xạ các tên ghi nhớ vào ký tự. Lưu ý: bạn nên sử dụng chr() trong py3k - unichr() không phải là một tên hợp lệ nữa
    • Ngoài ra, bạn có thể cập nhật XMLParser.entity với html.entities.html5 để lập bản đồ tất cả các thực HTML5 ghi nhớ hợp lệ để nhân vật của họ.
  • Nếu dữ liệu là XHTML, bạn có thể phân lớp HTMLParser để xử lý các đối tượng ghi nhớ nhưng điều này sẽ không trở lại một ElementTree như mong muốn.

Dưới đây là đoạn mã tôi đã sử dụng - nó phân tích XML với một DOCTYPE bên ngoài thông qua HTMLParser (để chứng minh làm thế nào để thêm xử lý thực thể bằng cách subclassing), ET.XMLParser với ánh xạ thực thể và expat (mà sẽ chỉ âm thầm bỏ qua các đối tượng không xác định do DOCTYPE bên ngoài). Có một thực thể XML hợp lệ (&gt;) và một thực thể không xác định (&copy;) mà tôi ánh xạ tới chr(0x24B4) với ET.XMLParser.

from html.parser import HTMLParser 
from html.entities import name2codepoint 
import xml.etree.ElementTree as ET 
import xml.parsers.expat as expat 

xml = '''<?xml version="1.0"?> 
<!DOCTYPE data PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 
<data> 
    <country name="Liechtenstein"> 
     <rank>1&gt;</rank> 
     <year>2008&copy;</year> 
     <gdppc>141100</gdppc> 
     <neighbor name="Austria" direction="E"/> 
     <neighbor name="Switzerland" direction="W"/> 
    </country> 
</data>''' 

# HTMLParser subclass which handles entities 
print('=== HTMLParser') 
class MyHTMLParser(HTMLParser): 
    def handle_starttag(self, name, attrs): 
     print('Start element:', name, attrs) 
    def handle_endtag(self, name): 
     print('End element:', name) 
    def handle_data(self, data): 
     print('Character data:', repr(data)) 
    def handle_entityref(self, name): 
     self.handle_data(chr(name2codepoint[name])) 

htmlparser = MyHTMLParser() 
htmlparser.feed(xml) 


# ET.XMLParser parse 
print('=== XMLParser') 
parser = ET.XMLParser() 
parser.entity['copy'] = chr(0x24B8) 
root = ET.fromstring(xml, parser) 
print(ET.tostring(root)) 
for elem in root: 
    print(elem.tag, ' - ', elem.attrib) 
    for subelem in elem: 
     print(subelem.tag, ' - ', subelem.attrib, ' - ', subelem.text) 

# Expat parse 
def start_element(name, attrs): 
    print('Start element:', name, attrs) 
def end_element(name): 
    print('End element:', name) 
def char_data(data): 
    print('Character data:', repr(data)) 
print('=== Expat') 
expatparser = expat.ParserCreate() 
expatparser.StartElementHandler = start_element 
expatparser.EndElementHandler = end_element 
expatparser.CharacterDataHandler = char_data 
expatparser.Parse(xml) 
+1

Cảm ơn bạn đã khám phá vấn đề, nhưng tôi bằng cách nào đó nghi ngờ rằng Python 3.x SPL không cho phép cập nhật bảng thực thể XML. Ít nhất tôi không thể tìm thấy thông báo như vậy. Rất tiếc, nhưng việc sử dụng regex để chuẩn bị dữ liệu XHTML từ xa không được chấp nhận làm ý tưởng. – theta

+0

Tôi đã có nhiều thời gian hơn để chơi với điều này và tìm ra lý do tại sao người nước ngoài không chuyển sang phương thức '_default()' trong 'XMLParser'. Xem bản chỉnh sửa của tôi - bạn có thể là các thực thể bản đồ được cung cấp DOCTYPE bên ngoài được xác định. –

+0

Tôi muốn câu hỏi này tổng quát hơn, nhưng hãy lọc vấn đề "cục bộ" hơn: Tôi có dữ liệu XHTML với 'xhtml1-transitional.dtd' và tôi biết trước rằng thực thể XML chưa xác định là'   '. Tôi sử dụng lxml theo mặc định và nếu không có sẵn dự phòng cho SPL, nhưng trả về ET. – theta

2

Tôi gặp sự cố tương tự và gặp vấn đề này bằng cách sử dụng lxml. etree.XMLParser của nó có một đối số từ khóa recover buộc nó phải cố gắng bỏ qua XML bị hỏng.

Các vấn đề liên quan