Como executo decodificação / codificação HTML usando Python / Django?

127

Eu tenho uma seqüência de caracteres que é codificada em HTML:

'''<img class="size-medium wp-image-113"\
 style="margin-left: 15px;" title="su1"\
 src="http://blah.org/wp-content/uploads/2008/10/su1-300x194.jpg"\
 alt="" width="300" height="194" />'''

Eu quero mudar isso para:

<img class="size-medium wp-image-113" style="margin-left: 15px;" 
  title="su1" src="http://blah.org/wp-content/uploads/2008/10/su1-300x194.jpg" 
  alt="" width="300" height="194" /> 

Quero que isso seja registrado como HTML para que seja renderizado como uma imagem pelo navegador em vez de ser exibido como texto.

A cadeia de caracteres é armazenada assim porque estou usando uma ferramenta de raspagem da Web chamada BeautifulSoup, "varre" uma página da Web e obtém determinado conteúdo, depois retorna a string nesse formato.

Eu descobri como fazer isso em C #, mas não em Python . Alguém pode me ajudar?

Relacionado

rksprst
fonte

Respostas:

118

Dado o caso de uso do Django, há duas respostas para isso. Aqui está sua django.utils.html.escapefunção, para referência:

def escape(html):
    """Returns the given HTML with ampersands, quotes and carets encoded."""
    return mark_safe(force_unicode(html).replace('&', '&amp;').replace('<', '&l
t;').replace('>', '&gt;').replace('"', '&quot;').replace("'", '&#39;'))

Para reverter isso, a função Cheetah descrita na resposta de Jake deve funcionar, mas está faltando aspas simples. Esta versão inclui uma tupla atualizada, com a ordem de substituição invertida para evitar problemas simétricos:

def html_decode(s):
    """
    Returns the ASCII decoded version of the given HTML string. This does
    NOT remove normal HTML tags like <p>.
    """
    htmlCodes = (
            ("'", '&#39;'),
            ('"', '&quot;'),
            ('>', '&gt;'),
            ('<', '&lt;'),
            ('&', '&amp;')
        )
    for code in htmlCodes:
        s = s.replace(code[1], code[0])
    return s

unescaped = html_decode(my_string)

Esta, no entanto, não é uma solução geral; é apropriado apenas para cadeias codificadas com django.utils.html.escape. De maneira mais geral, é uma boa ideia manter a biblioteca padrão:

# Python 2.x:
import HTMLParser
html_parser = HTMLParser.HTMLParser()
unescaped = html_parser.unescape(my_string)

# Python 3.x:
import html.parser
html_parser = html.parser.HTMLParser()
unescaped = html_parser.unescape(my_string)

# >= Python 3.5:
from html import unescape
unescaped = unescape(my_string)

Como sugestão: pode fazer mais sentido armazenar o HTML sem escape no seu banco de dados. Valeria a pena procurar obter resultados não escapados do BeautifulSoup, se possível, e evitar esse processo completamente.

Com o Django, a fuga ocorre apenas durante a renderização do modelo; para evitar escapar, basta dizer ao mecanismo de modelo para não escapar da corda. Para fazer isso, use uma destas opções no seu modelo:

{{ context_var|safe }}
{% autoescape off %}
    {{ context_var }}
{% endautoescape %}
Daniel Naab
fonte
1
Por que não usar Django ou Cheetah?
7772 Mat
4
Não existe oposto ao django.utils.html.escape?
7772 Mat
12
Eu acho que escapar apenas ocorre no Django durante a renderização do modelo. Portanto, não há necessidade de uma fuga - basta dizer ao mecanismo de modelo para não escapar. quer {{context_var | segura}} ou {% autoescape off%} {{context_var}} {% endautoescape%}
Daniel Naab
3
@ Daniel: Por favor, mude seu comentário para uma resposta para que eu possa votar! | safe era exatamente o que eu (e tenho certeza que outros) estava procurando em resposta a esta pergunta.
23411 Wayne Koorts
1
html.parser.HTMLParser().unescape()foi descontinuado em 3.5. Use em html.unescape()vez disso.
Pjvandehaar
114

Com a biblioteca padrão:

  • Escape de HTML

    try:
        from html import escape  # python 3.x
    except ImportError:
        from cgi import escape  # python 2.x
    
    print(escape("<"))
    
  • Desescape HTML

    try:
        from html import unescape  # python 3.4+
    except ImportError:
        try:
            from html.parser import HTMLParser  # python 3.x (<3.4)
        except ImportError:
            from HTMLParser import HTMLParser  # python 2.x
        unescape = HTMLParser().unescape
    
    print(unescape("&gt;"))
    
Jiangge Zhang
fonte
12
Eu acho que essa é a resposta mais direta, 'incluída na bateria' e correta. Não sei por que as pessoas votam no Django / Cheetah.
27512 Daniel Baktiar
Eu também penso, exceto que esta resposta não parece completa. HTMLParserprecisa ser subclassificado, informado sobre o que fazer com todas as partes de qualquer objeto em que ele é alimentado e depois alimentado o objeto a ser analisado, como visto aqui . Além disso, você ainda desejará usar o name2codepointdict para converter cada identidade html no caractere real que ela representa.
Marconius
Você está certo. O não classificado HTMLParsernão poderia funcionar como desejávamos, se colocarmos uma entidade HTML nela. Talvez eu deva renomear htmlparserpara _htmlparser, a fim de ocultá-lo, e apenas expor o unescapemétodo para ser como uma função auxiliar.
Jiangge Zhang
3
Uma observação para o ano de 2015, HTMLParser.unescape foi preterida na py 3.4 e removida na 3.5. usar from html import unescape, em vez
Karolis Ryselis
2
Observe que isso não manipula caracteres especiais como German Umlauts ("Ü")
576i
80

Para codificação html, há o cgi.escape da biblioteca padrão:

>> help(cgi.escape)
cgi.escape = escape(s, quote=None)
    Replace special characters "&", "<" and ">" to HTML-safe sequences.
    If the optional flag quote is true, the quotation mark character (")
    is also translated.

Para decodificação html, eu uso o seguinte:

import re
from htmlentitydefs import name2codepoint
# for some reason, python 2.5.2 doesn't have this one (apostrophe)
name2codepoint['#39'] = 39

def unescape(s):
    "unescape HTML code refs; c.f. http://wiki.python.org/moin/EscapingHtml"
    return re.sub('&(%s);' % '|'.join(name2codepoint),
              lambda m: unichr(name2codepoint[m.group(1)]), s)

Para algo mais complicado, eu uso BeautifulSoup.

user26294
fonte
20

Use a solução de daniel se o conjunto de caracteres codificados for relativamente restrito. Caso contrário, use uma das inúmeras bibliotecas de análise de HTML.

Eu gosto do BeautifulSoup porque ele pode lidar com XML / HTML malformado:

http://www.crummy.com/software/BeautifulSoup/

para sua pergunta, há um exemplo na documentação deles

from BeautifulSoup import BeautifulStoneSoup
BeautifulStoneSoup("Sacr&eacute; bl&#101;u!", 
                   convertEntities=BeautifulStoneSoup.HTML_ENTITIES).contents[0]
# u'Sacr\xe9 bleu!'
vincent
fonte
Não BeautifulSoup não converter entidades hexagonais (& # x65;) stackoverflow.com/questions/57708/...
jfs
1
Para BeautifulSoup4, o equivalente seria:from bs4 import BeautifulSoup BeautifulSoup("Sacr&eacute; bl&#101;u!").contents[0]
radicand 27/01
10

No Python 3.4 ou superior:

import html

html.unescape(your_string)
Collin Anderson
fonte
8

Veja na parte inferior desta página no wiki do Python , há pelo menos duas opções para "desescape" do html.

zgoda
fonte
6

O comentário de Daniel como resposta:

"a fuga ocorre apenas no Django durante a renderização do modelo. Portanto, não há necessidade de uma remoção de escape - basta dizer ao mecanismo de modelagem para não escapar. {{context_var | safe}} ou {% autoescape off%} {{context_var}} { % endautoescape%} "

dfrankow
fonte
Funciona, exceto que minha versão do Django não possui 'safe'. Eu uso 'escape' em vez disso. Presumo que é a mesma coisa.
willem
1
@ willem: eles são o oposto!
Asherah
5

Encontrei uma boa função em: http://snippets.dzone.com/posts/show/4569

def decodeHtmlentities(string):
    import re
    entity_re = re.compile("&(#?)(\d{1,5}|\w{1,8});")

    def substitute_entity(match):
        from htmlentitydefs import name2codepoint as n2cp
        ent = match.group(2)
        if match.group(1) == "#":
            return unichr(int(ent))
        else:
            cp = n2cp.get(ent)

            if cp:
                return unichr(cp)
            else:
                return match.group()

    return entity_re.subn(substitute_entity, string)[0]
slowkvant
fonte
A vantagem de usar re é que você pode combinar os dois & # 039; e & # 39; usando a mesma pesquisa.
Neal Stublen 15/10/10
Isso não lida com o &#xA0;que deve decodificar para a mesma coisa que &#160;e &nbsp;.
Mike Samuel
3

Se alguém estiver procurando uma maneira simples de fazer isso através dos modelos django, você sempre pode usar filtros como este:

<html>
{{ node.description|safe }}
</html>

Eu tinha alguns dados provenientes de um fornecedor e tudo o que publiquei tinha tags html realmente escritas na página renderizada, como se você estivesse olhando a fonte. O código acima me ajudou muito. Espero que isso ajude os outros.

Felicidades!!

Chris Harty
fonte
3

Mesmo que essa seja uma pergunta muito antiga, isso pode funcionar.

Django 1.5.5

In [1]: from django.utils.text import unescape_entities
In [2]: unescape_entities('&lt;img class=&quot;size-medium wp-image-113&quot; style=&quot;margin-left: 15px;&quot; title=&quot;su1&quot; src=&quot;http://blah.org/wp-content/uploads/2008/10/su1-300x194.jpg&quot; alt=&quot;&quot; width=&quot;300&quot; height=&quot;194&quot; /&gt;')
Out[2]: u'<img class="size-medium wp-image-113" style="margin-left: 15px;" title="su1" src="http://blah.org/wp-content/uploads/2008/10/su1-300x194.jpg" alt="" width="300" height="194" />'
James
fonte
1
Este foi o único que foi capaz de decodificar pares substitutos codificados como entidades html, como "&#55349;&#56996;". Depois de outro result.encode('utf-16', 'surrogatepass').decode('utf-16'), finalmente recebi o original de volta.
Rescisk #
1

Encontrei isso no código-fonte Cheetah ( aqui )

htmlCodes = [
    ['&', '&amp;'],
    ['<', '&lt;'],
    ['>', '&gt;'],
    ['"', '&quot;'],
]
htmlCodesReversed = htmlCodes[:]
htmlCodesReversed.reverse()
def htmlDecode(s, codes=htmlCodesReversed):
    """ Returns the ASCII decoded version of the given HTML string. This does
        NOT remove normal HTML tags like <p>. It is the inverse of htmlEncode()."""
    for code in codes:
        s = s.replace(code[1], code[0])
    return s

não sei por que eles invertem a lista, acho que isso tem a ver com a maneira como codificam, portanto, com você, talvez não seja necessário revertê-lo. Além disso, se eu fosse você, eu mudaria o htmlCodes para ser uma lista de tuplas, em vez de uma lista de listas ... isso acontece na minha biblioteca :)

Notei que seu título também pediu codificação, então aqui está a função de codificação do Cheetah.

def htmlEncode(s, codes=htmlCodes):
    """ Returns the HTML encoded version of the given string. This is useful to
        display a plain ASCII text string on a web page."""
    for code in codes:
        s = s.replace(code[0], code[1])
    return s
Jake
fonte
2
A lista é revertida porque as substituições de decodificação e codificação sempre precisam ser feitas simetricamente. Sem a inversão, você poderia, por exemplo. converter '& lt;' para '& lt;', depois, na próxima etapa, converta-o incorretamente para '<'.
9990
1

Você também pode usar o django.utils.html.escape

from django.utils.html import escape

something_nice = escape(request.POST['something_naughty'])
Seth Gottlieb
fonte
O OP perguntou sobre não escapar, não escapar.
Claymation
No título em si, ele também pediu codificação - acabou de encontrar sua resposta e sou grato por isso.
Simon Steinberger
1
Não é o que o OP pediu, mas achei isso útil.
rectangletangle
0

Abaixo está uma função python que usa o módulo htmlentitydefs. Não é perfeito. A versão htmlentitydefsque eu tenho está incompleta e assume que todas as entidades decodificam para um ponto de código errado para entidades como &NotEqualTilde;:

http://www.w3.org/TR/html5/named-character-references.html

NotEqualTilde;     U+02242 U+00338    ≂̸

Com essas advertências, aqui está o código.

def decodeHtmlText(html):
    """
    Given a string of HTML that would parse to a single text node,
    return the text value of that node.
    """
    # Fast path for common case.
    if html.find("&") < 0: return html
    return re.sub(
        '&(?:#(?:x([0-9A-Fa-f]+)|([0-9]+))|([a-zA-Z0-9]+));',
        _decode_html_entity,
        html)

def _decode_html_entity(match):
    """
    Regex replacer that expects hex digits in group 1, or
    decimal digits in group 2, or a named entity in group 3.
    """
    hex_digits = match.group(1)  # '&#10;' -> unichr(10)
    if hex_digits: return unichr(int(hex_digits, 16))
    decimal_digits = match.group(2)  # '&#x10;' -> unichr(0x10)
    if decimal_digits: return unichr(int(decimal_digits, 10))
    name = match.group(3)  # name is 'lt' when '&lt;' was matched.
    if name:
        decoding = (htmlentitydefs.name2codepoint.get(name)
            # Treat &GT; like &gt;.
            # This is wrong for &Gt; and &Lt; which HTML5 adopted from MathML.
            # If htmlentitydefs included mappings for those entities,
            # then this code will magically work.
            or htmlentitydefs.name2codepoint.get(name.lower()))
        if decoding is not None: return unichr(decoding)
    return match.group(0)  # Treat "&noSuchEntity;" as "&noSuchEntity;"
Mike Samuel
fonte
0

Essa é a solução mais fácil para esse problema -

{% autoescape on %}
   {{ body }}
{% endautoescape %}

A partir desta página .

smilitude
fonte
0

Pesquisando a solução mais simples dessa questão no Django e Python, descobri que você pode usar as funções internas para escapar / retirar o código html.

Exemplo

Salvei seu código html scraped_htmle clean_html:

scraped_html = (
    '&lt;img class=&quot;size-medium wp-image-113&quot; '
    'style=&quot;margin-left: 15px;&quot; title=&quot;su1&quot; '
    'src=&quot;http://blah.org/wp-content/uploads/2008/10/su1-300x194.jpg&quot; '
    'alt=&quot;&quot; width=&quot;300&quot; height=&quot;194&quot; /&gt;'
)
clean_html = (
    '<img class="size-medium wp-image-113" style="margin-left: 15px;" '
    'title="su1" src="http://blah.org/wp-content/uploads/2008/10/su1-300x194.jpg" '
    'alt="" width="300" height="194" />'
)

Django

Você precisa do Django> = 1.0

escapar

Para desescape seu código html raspado, você pode usar django.utils.text.unescape_entities que:

Converta todas as referências de caracteres nomeados e numéricos nos caracteres unicode correspondentes.

>>> from django.utils.text import unescape_entities
>>> clean_html == unescape_entities(scraped_html)
True

escapar

Para escapar do seu código html limpo, você pode usar o django.utils.html.escape que:

Retorna o texto fornecido com e comercial, aspas e colchetes angulares codificados para uso em HTML.

>>> from django.utils.html import escape
>>> scraped_html == escape(clean_html)
True

Pitão

Você precisa de Python> = 3.4

escapar

Para remover o escape do código html raspado, você pode usar o html.unescape que:

Converter todas as referências nomeadas e numérico de caracteres (por exemplo &gt;, &#62;, &x3e;) na cadeia de s para os caracteres Unicode correspondentes.

>>> from html import unescape
>>> clean_html == unescape(scraped_html)
True

escapar

Para escapar do seu código html limpo, você pode usar o html.escape que:

Converter os caracteres &, <e >na string s para seqüências de HTML-seguras.

>>> from html import escape
>>> scraped_html == escape(clean_html)
True
Paolo Melchiorre
fonte