2012-05-18 12 views
8

Tôi đang cố gắng để phân tích, thao tác và đầu ra HTML sử dụng Python của ElementTree:Python ElementTree sẽ không chuyển đổi không bị phá hủy không gian khi sử dụng UTF-8 cho đầu ra

import sys 
from cStringIO import StringIO 
from xml.etree import ElementTree as ET 
from htmlentitydefs import entitydefs 

source = StringIO("""<html> 
<body> 
<p>Less than &lt;</p> 
<p>Non-breaking space &nbsp;</p> 
</body> 
</html>""") 

parser = ET.XMLParser() 
parser.parser.UseForeignDTD(True) 
parser.entity.update(entitydefs) 
etree = ET.ElementTree() 

tree = etree.parse(source, parser=parser) 
for p in tree.findall('.//p'): 
    print ET.tostring(p, encoding='UTF-8') 

Khi tôi chạy này sử dụng Python 2.7 trên Mac OS X 10.6, tôi nhận được:

<p>Less than &lt;</p> 

Traceback (most recent call last): 
    File "bar.py", line 20, in <module> 
    print ET.tostring(p, encoding='utf-8') 
    File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/xml/etree/ElementTree.py", line 1120, in tostring 
    ElementTree(element).write(file, encoding, method=method) 
    File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/xml/etree/ElementTree.py", line 815, in write 
    serialize(write, self._root, encoding, qnames, namespaces) 
    File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/xml/etree/ElementTree.py", line 931, in _serialize_xml 
    write(_escape_cdata(text, encoding)) 
    File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/xml/etree/ElementTree.py", line 1067, in _escape_cdata 
    return text.encode(encoding, "xmlcharrefreplace") 
UnicodeDecodeError: 'ascii' codec can't decode byte 0xa0 in position 19: ordinal not in range(128) 

tôi nghĩ rằng việc xác định "encoding = 'UTF-8'" sẽ chăm sóc của các nhân vật không gian không bị phá hủy, nhưng dường như nó không. Tôi nên làm gì?

Trả lời

3

XML chỉ xác định &lt;, &gt;, &apos;, &quot;&amp;. &nbsp; và những người khác đến từ HTML. Vì vậy, bạn có một vài lựa chọn.

  1. Bạn có thể thay đổi nguồn của bạn để sử dụng các đối tượng số, như &#160; hoặc &#xA0; cả hai đều là tương đương với &nbsp;.
  2. Bạn có thể sử dụng DTD để xác định các giá trị đó.

Có một số thông tin hữu ích (thông tin được viết về XSLT, nhưng XSLT được viết bằng XML, do đó, cùng áp dụng) tại XSLT FAQ.


Câu hỏi hiện xuất hiện để bao gồm dấu vết ngăn xếp; điều đó thay đổi mọi thứ. Bạn có chắc chắn rằng chuỗi là trong UTF-8? Nếu nó giải quyết thành một byte đơn 0xA0, thì nó không phải là UTF-8 nhưng nhiều khả năng là cp1252 hoặc iso-8859-1.

+0

Vấn đề không phải là trên đầu vào: thủ thuật UseForeignDTD hoạt động tốt cho điều đó. Vấn đề là trên đầu ra: văn bản trong bộ nhớ chứa 0xA0, mà tôi mong đợi sẽ được chuyển đổi sang biểu diễn UTF-8 của nó bằng ET.tostring (vì tôi đã nói 'encoding = "UTF-8"'). –

-1

HTML không giống với XML, vì vậy các thẻ như &nbsp; sẽ không hoạt động. Lý tưởng nhất, nếu bạn đang cố gắng để vượt qua những thông tin qua XML, bạn có thể đầu tiên xml mã hóa các dữ liệu trên, vì vậy nó sẽ giống như thế này:

<xml> 
<mydata> 
&lt;htm&gt; 
&lt;body&gt; 
&lt;p&gt;Less than &amp;lt;&lt;/p&gt; 
&lt;p&gt;Non-breaking space &amp;nbsp;&lt;/p&gt; 
&lt;/body&gt; 
&lt;/html&gt; 
</mydata> 
</xml> 

Và sau phân tích cú pháp XML, bạn có thể HTML unencode sự chuỗi.

+0

Vấn đề không phải là trên đầu vào: thủ thuật UseForeignDTD hoạt động tốt cho điều đó. Vấn đề là trên đầu ra: văn bản trong bộ nhớ chứa 0xA0, mà tôi mong đợi sẽ được chuyển đổi sang biểu diễn UTF-8 của nó bằng ET.tostring (vì tôi đã nói 'encoding = "UTF-8"'). –

-1

Tôi nghĩ rằng sự cố bạn có ở đây không phải với thực thể nbsp của bạn mà là với bản in của bạn.

lỗi của bạn là:

UnicodeDecodeError: 'ascii' codec can't decode byte 0xa0 in position 19: ordinal not in range(128)

Tôi nghĩ rằng đây là bởi vì bạn đang tham gia một chuỗi utf-8 (từ ET.tostring(p, encoding='utf-8')) và cố gắng để echo nó ra trong một thiết bị đầu cuối ascii. Vì vậy, Python đang ngầm chuyển đổi chuỗi đó thành unicode rồi chuyển đổi nó thành ascii. Mặc dù nbsp có thể được đại diện trực tiếp trong utf-8, nhưng không thể hiển thị trực tiếp trong ascii. Do đó lỗi.

Hãy thử lưu đầu ra vào tệp thay vào đó và xem bạn có nhận được những gì bạn mong đợi hay không.

Hoặc, hãy thử print ET.toString(p, encoding='ascii'), điều này sẽ khiến ElementTree sử dụng các thực thể ký tự số để đại diện cho bất kỳ thứ gì không thể được biểu diễn bằng ascii.

+0

Lưu đầu ra vào một tệp không có hiệu lực: nếu tôi mở tệp bằng cách sử dụng "output = open ('temp.txt', 'w')" và sau đó sử dụng "output.write (ET.tostring (p, encoding = ' ascii ')) ", tôi nhận được lỗi tương tự. –

6

0xA0 là ký tự latin1, không phải ký tự unicode và giá trị p.văn bản trong vòng lặp là một str và không unicode, có nghĩa là để mã hóa nó trong utf-8, trước tiên nó phải được chuyển đổi bởi Python ngầm thành một chuỗi unicode (tức là sử dụng giải mã). Khi nó đang làm điều này nó giả định ascii vì nó không được nói gì khác. 0xa0 không phải là ký tự ascii hợp lệ, nhưng nó là ký tự latin1 hợp lệ.

Lý do bạn có ký tự latin1 thay vì ký tự unicode là do entitydefs là ánh xạ tên cho các chuỗi mã hóa latin1. Bạn cần điểm mã unicode mà bạn có thể nhận được từ htmlentitydef.name2codepoint

Phiên bản dưới đây sẽ sửa chữa nó cho bạn:

import sys 
from cStringIO import StringIO 
from xml.etree import ElementTree as ET 
from htmlentitydefs import name2codepoint 

source = StringIO("""<html> 
<body> 
<p>Less than &lt;</p> 
<p>Non-breaking space &nbsp;</p> 
</body> 
</html>""") 

parser = ET.XMLParser() 
parser.parser.UseForeignDTD(True) 
parser.entity.update((x, unichr(i)) for x, i in name2codepoint.iteritems()) 
etree = ET.ElementTree() 

tree = etree.parse(source, parser=parser) 
for p in tree.findall('.//p'): 
    print ET.tostring(p, encoding='UTF-8') 
+0

Đây là câu trả lời đúng! Để đặt chính xác hơn, 'htmlentitydefs.entitydefs' là xấu. Điều này khiến chuỗi byte được thêm vào ElementTree của bạn, nơi chỉ nên có chuỗi unicode. Rất tiếc, lỗi này không hiển thị cho đến sau này. –

3

bạn &nbsp; đang được chuyển đổi sang '\ xa0' là mặc định (ascii) mã hóa cho một không gian nonbreaking (bảng mã UTF-8 là '\ xC2 \ xa0'.) dòng

'\xa0'.encode('utf-8') 

kết quả trong một UnicodeDecodeError, bởi vì các codec mặc định, ascii, chỉ hoạt động lên đến 128 ký tự và ord ('\ xa0') = 160. Đặt mã hóa mặc định thành đôi khác, ví dụ:

import sys 
reload(sys) # must reload sys to use 'setdefaultencoding' 
sys.setdefaultencoding('latin-1') 

print '\xa0'.encode('utf-8', "xmlcharrefreplace") 

nên giải quyết vấn đề của bạn.

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