Замена амперсандов, являющихся частью числовой символьной ссылки, на элементное дерево Python - PullRequest
3 голосов
/ 14 февраля 2012

Я использую модуль elementtree в Python для написания XML (я использую Python 2.7 и 3.2). Текстовые поля некоторых из моих элементов содержат числовые ссылки на символы.

Однако, как только я использую tostring в elementtree, все амперсанды в ссылках на символы заменяются на &. Очевидно, elementtree или основной синтаксический анализатор не распознают, что амперсанды здесь являются частью числовой символьной ссылки.

После некоторого поиска я нашел это: elementtree и entity

Однако я тоже не заинтересован в этом, так как в моем текущем коде я предвижу, что это может привести к возникновению собственных проблем. Кроме этого я обнаружил на удивление мало, так что, может быть, я просто упускаю из виду нечто очевидное?

Следующий простой тестовый код иллюстрирует проблему (протестирован с использованием Python 2.7 и 3.2):

import sys
import xml.etree.ElementTree as ET

def main():
    # Text string that contains numeric character reference
    someText = "Ström"

    # Create element object
    testElement = ET.Element('rubbish')

    # Add someText to element's text attribute
    testElement.text = someText

    # Convert element to xml-formatted text string 
    testElementAsString = ET.tostring(testElement,'ascii', 'xml')

    print(testElementAsString)

   # Result: ampersand replaced with '&amp;': <rubbish>Str&amp;#246;m</rubbish>

main()

Если у кого-то есть идеи или предложения, которые были бы великолепны!

Ответы [ 2 ]

2 голосов
/ 16 февраля 2012

Краткое обновление к вышесказанному: я только что еще раз критически посмотрел на мой код и понял, что есть еще более простое решение (в значительной степени основанное на ответе @ Дункана), которое, по крайней мере, работает для меня.

В моем исходном коде я использовал ссылки на сущности, чтобы получить представление ASCII некоторого текста в кодировке Latin-15 (который я читал из двоичного файла).Таким образом, вышеприведенная переменная someText фактически начала свою жизнь как байтовый объект, который впоследствии был декодирован в текст Latin-15 и, наконец, преобразован в ASCII.

Благодаря @Duncan и @Inerdial теперь я знаю, что ElementTree может выполнять преобразование Latin-15 в ASCII само по себе.После некоторых экспериментов мне удалось придумать решение, которое было бы глупо простым, хотя и почти тривиальным.Тем не менее, я думаю, что это просто может быть полезным для некоторых, поэтому я решил поделиться этим здесь в любом случае:

import sys
import xml.etree.ElementTree as ET

def main():
    # Bytes object
    someBytes=b'Str\xf6m'

    # Decode to Latin-15
    someText=someBytes.decode('iso-8859-15','strict')

    # Create element object
    testElement=ET.Element('rubbish')

    # Add someText to element's text attribute
    testElement.text=someText

    # Convert element to xml-formatted text string 
    testElementAsString=ET.tostring(testElement,'ascii', 'xml').decode('ascii')

    print(testElementAsString)

main()

Обратите внимание, что я добавил окончательный .decode("ascii"), чтобы сделатьэта работа с Python 3 (который, в отличие от Python 2.7, возвращает testElementAsString как объект байтов).

Еще раз спасибо @Duncan, @Inerdial и @Tomalak за указание мне в правильном направлении, и @RikПогги за исправление форматирования в моем оригинальном посте!

2 голосов
/ 14 февраля 2012

Вам необходимо декодировать ссылки на символы в вашем вводе. Вот функция, которая будет декодировать как числовые ссылки на символы, так и именованные ссылки html; он принимает байтовую строку в качестве входных данных и возвращает юникод. Код ниже работает для Python 2.7 или 3.x.

import re
try:
    from htmlentitydefs import name2codepoint
except ImportError:
    # Must be Python 3.x
    from html.entities import name2codepoint
    unichr = chr

name2codepoint = name2codepoint.copy()
name2codepoint['apos']=ord("'")

EntityPattern = re.compile('&(?:#(\d+)|(?:#x([\da-fA-F]+))|([a-zA-Z]+));')

def decodeEntities(s, encoding='utf-8'):
    def unescape(match):
        code = match.group(1)
        if code:
            return unichr(int(code, 10))
        else:
            code = match.group(2)
            if code:
                return unichr(int(code, 16))
            else:
                code = match.group(3)
                if code in name2codepoint:
                    return unichr(name2codepoint[code])
        return match.group(0)

    return EntityPattern.sub(unescape, s.decode(encoding))

someText = decodeEntities(b"Str&#246;m")
print(someText)

Конечно, если вы не сможете получить ссылку на символ в строке для начала, это несколько облегчит вашу жизнь.

...