2012-05-18 2 views
8

Я пытаюсь разобрать, манипулировать, и вывод HTML с использованием ElementTree Питона:Python ElementTree не преобразует неразрывные пробелы при использовании UTF-8 для вывода

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

Когда я бегу это с помощью Python 2.7 на Mac OS X 10.6, я получаю:

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

Я думал, что определение «кодирование =" UTF-8»будет заботиться о неразрывном пробеле, но, видимо, это не так. Что мне делать вместо этого?

ответ

3

XML определяет только &lt;, &gt;, &apos;, &quot; и &amp;. &nbsp; и другие - из HTML. Таким образом, у вас есть несколько вариантов.

  1. Вы можете изменить исходные тексты для использования цифровых объектов, как &#160; или &#xA0; оба из которых эквивалентны &nbsp;.
  2. Вы можете использовать DTD, который определяет эти значения.

Есть некоторая полезная информация (она написана о XSLT, но XSLT написана с использованием XML, поэтому то же самое относится) на XSLT FAQ.


Теперь возникает вопрос о включении трассировки стека; что меняет вещи. Вы уверены, что строка находится в UTF-8? Если он решает один байт 0xA0, то это не UTF-8, но более вероятно cp1252 или iso-8859-1.

+0

Проблема не на входе: трюк UseForeignDTD отлично подходит для этого. Проблема заключается в выходе: текст в памяти содержит 0xA0, который, как я ожидал, будет преобразован в его представление UTF-8 с помощью ET.tostring (так как я сказал «encoding =« UTF-8 »). –

-1

HTML не совпадает с XML, поэтому теги, такие как &nbsp;, не работают. В идеале, если вы пытаетесь передать эту информацию через XML, вы можете первым XML-кодирования вышеприведенные данные, так что это будет выглядеть примерно так:

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

а затем после разбора XML вы можете HTML-unencode строка.

+0

Проблема не на входе: трюк UseForeignDTD отлично подходит для этого. Проблема заключается в выходе: текст в памяти содержит 0xA0, который, как я ожидал, будет преобразован в его представление UTF-8 с помощью ET.tostring (так как я сказал «encoding =« UTF-8 »). –

-1

Я думаю, что проблема, с которой вы здесь сталкиваетесь, связана не с вашим nbsp сущностью, а с вашим заявлением на печать.

Ваша ошибка:

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

Я думаю, что это происходит потому, что вы принимаете строку UTF-8 (от ET.tostring(p, encoding='utf-8')) и пытается повторить его в текстовом терминале. Таким образом, Python неявно преобразовывает эту строку в unicode, а затем преобразует ее снова в ascii. Хотя nbsp может быть представлен непосредственно в utf-8, это не может быть представлен непосредственно в ascii. Отсюда и ошибка.

Попробуйте сохранить вывод в файл и посмотрите, получаете ли вы то, что ожидаете.

В качестве альтернативы попробуйте print ET.toString(p, encoding='ascii'), что должно привести к тому, что ElementTree будет использовать числовые символьные сущности для представления всего, что не может быть представлено с помощью ascii.

+0

Сохранение вывода в файл не влияет: если я открываю файл, используя «output = open (« temp.txt »,« w »)», а затем используйте «output.write (ET.tostring (p, encoding = ' ascii ')) ", я получаю ту же ошибку. –

6

0xA0 является символом latin1, а не символом юникода и значением p.текст в цикле является str, а не unicode, это означает, что для его кодирования в utf-8 он должен быть сначала преобразован Python неявно в строку unicode (то есть с использованием декодирования). Когда он это делает, он принимает ascii, поскольку ему ничего не сказано. 0xa0 не является допустимым символом ascii, но является допустимым латинским символом.

Причина, по которой у вас есть символы latin1 вместо символов юникода, состоит в том, что entitydefs является отображением имен в строки кодирования latin1. Вам нужна точка Юникода код, который вы можете получить от htmlentitydef.name2codepoint

версии ниже, должны исправить это для вас:

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

Это правильный ответ! Говоря более кратко, «htmlentitydefs.entitydefs» плохо. Это приводит к тому, что строки байтов добавляются в ваш ElementTree, где должны быть только строки Unicode. И, к сожалению, ошибка не появляется до конца. –

3

Ваш &nbsp; преобразовывается в «\ xA0», который по умолчанию (ASCII), кодирующий неразрывный пробел (кодировка UTF-8 является '\ xc2 \ xA0.) линия

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

приводит к UnicodeDecodeError, потому что кодек по умолчанию, ASCII, только работает до 128 символов и Орд ('\ xa0') = 160. Установка кодировки по умолчанию для somet hing else, то есть:

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

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

должен решить вашу проблему.

Смежные вопросы