Python ElementTree не будет преобразовывать неразрывные пробелы при использовании UTF-8 для вывода

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

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 "" будет заботиться о неразрывном пробеле, но, очевидно, это не так. Что я должен сделать вместо этого?

Ответы на вопрос(5)

&lt;, &gt;, &apos;, &quot; а также&amp;. &nbsp; и другие приходят из HTML. Итак, у вас есть пара вариантов.

You can change your source to use numeric entities, like &#160; or &#xA0; both of which are equivalent to &nbsp;. You can use a DTD which defines those values.

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

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

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

что проблема у вас здесь не с вашей сущностью 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')) и пытается повторить это в терминале ASCII. Таким образом, Python неявно преобразует эту строку в юникод, а затем снова конвертирует ее в ascii. Хотя нбспcan быть представленным непосредственно в UTF-8, этоcannot быть представленными непосредственно в ascii. Отсюда и ошибка.

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

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

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

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

Причина, по которой вы используете символы латинского алфавита вместо символов юникода, заключается в том, что entitydefs - это отображение имен в строки латинского кодирования. Вам нужна кодовая точка Unicode, которую вы можете получить из 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')
 29 мая 2012 г., 20:33
Это правильный ответ! Короче говоря,htmlentitydefs.entitydefs плохо. Это приводит к добавлению байтовых строк в ваше ElementTree, где должны быть только строки Юникода. И, к сожалению, ошибка не появляется до позже.

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

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

приводит к UnicodeDecodeError, потому что кодек по умолчанию, ascii, работает только до 128 символов и ord ('a \ xa0') = 160. Установка кодировки по умолчанию на что-то другое, т.е.

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

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

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

&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-дешифровать строку.

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

Ваш ответ на вопрос