Comment effectuer le décodage / encodage HTML à l'aide de Python / Django?


127

J'ai une chaîne encodée en 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" />'''

Je veux changer cela en:

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

Je veux que cela soit enregistré au format HTML afin qu'il soit rendu comme une image par le navigateur au lieu d'être affiché sous forme de texte.

La chaîne est stockée comme ça parce que j'utilise un outil de web-scraping appelé BeautifulSoup, il "scanne" une page Web et en obtient un certain contenu, puis renvoie la chaîne dans ce format.

J'ai trouvé comment faire cela en C # mais pas en Python . Est-ce que quelqu'un peut m'aider?

en relation

Réponses:


118

Compte tenu du cas d'utilisation de Django, il y a deux réponses à cela. Voici sa django.utils.html.escapefonction, pour référence:

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

Pour inverser cela, la fonction Cheetah décrite dans la réponse de Jake devrait fonctionner, mais il manque le guillemet simple. Cette version inclut un tuple mis à jour, avec l'ordre de remplacement inversé pour éviter les problèmes symétriques:

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)

Ceci, cependant, n'est pas une solution générale; il n'est approprié que pour les chaînes encodées avec django.utils.html.escape. Plus généralement, c'est une bonne idée de s'en tenir à la bibliothèque standard:

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

À titre indicatif: il peut être plus judicieux de stocker le HTML sans échappement dans votre base de données. Il vaudrait la peine de chercher si possible à récupérer des résultats non échappés de BeautifulSoup et d'éviter complètement ce processus.

Avec Django, l'échappement se produit uniquement lors du rendu du modèle; donc pour éviter de vous échapper, dites simplement au moteur de création de modèles de ne pas échapper à votre chaîne. Pour ce faire, utilisez l'une de ces options dans votre modèle:

{{ context_var|safe }}
{% autoescape off %}
    {{ context_var }}
{% endautoescape %}

1
Pourquoi ne pas utiliser Django ou Cheetah?
Mat

4
N'y a-t-il aucun opposé de django.utils.html.escape?
Mat

12
Je pense que l'échappement ne se produit que dans Django lors du rendu du modèle. Par conséquent, il n'y a pas besoin d'un unescape - vous dites simplement au moteur de création de modèles de ne pas s'échapper. soit {{context_var | safe}} soit {% autoescape off%} {{context_var}} {% endautoescape%}
Daniel Naab

3
@Daniel: Veuillez changer votre commentaire en réponse afin que je puisse voter! | safe était exactement ce que je (et je suis sûr que d'autres) recherchaient en réponse à cette question.
Wayne Koorts

1
html.parser.HTMLParser().unescape()est obsolète dans la version 3.5. Utilisez html.unescape()plutôt.
pjvandehaar

114

Avec la bibliothèque standard:

  • HTML Escape

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

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

12
Je pense que c'est la réponse la plus simple, «batterie incluse» et correcte. Je ne sais pas pourquoi les gens votent pour ce truc Django / Cheetah.
Daniel Baktiar

Je le pense aussi, sauf que cette réponse ne semble pas complète. HTMLParserdoit être sous-classé, dit quoi faire de toutes les parties de tout objet auquel il est alimenté, puis alimenté l'objet à analyser, comme vu ici . En outre, vous souhaiterez toujours utiliser le name2codepointdict pour convertir chaque identité HTML en le caractère réel qu'elle représente.
Marconius

Vous avez raison. Les HTMLParsernon sous- classés ne pourraient pas fonctionner comme nous le souhaitions si nous y ajoutions une entité HTML. Peut - être que je devrais renommer htmlparserpour _htmlparserafin de le cacher, et exposer seulement la unescapeméthode pour être comme une fonction d'assistance.
Jiangge Zhang

3
Une note pour l'année 2015, HTMLParser.unescape est obsolète dans py 3.4 et supprimé dans 3.5. utiliser à la from html import unescapeplace
Karolis Ryselis

2
Notez que cela ne gère pas les caractères spéciaux comme les trémas allemands ("Ü")
576i

80

Pour l'encodage html, il y a cgi.escape de la bibliothèque standard:

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

Pour le décodage html, j'utilise ce qui suit:

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)

Pour tout ce qui est plus compliqué, j'utilise BeautifulSoup.


20

Utilisez la solution de Daniel si le jeu de caractères encodés est relativement restreint. Sinon, utilisez l'une des nombreuses bibliothèques d'analyse HTML.

J'aime BeautifulSoup car il peut gérer du XML / HTML malformé:

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

pour votre question, il y a un exemple dans leur documentation

from BeautifulSoup import BeautifulStoneSoup
BeautifulStoneSoup("Sacr&eacute; bl&#101;u!", 
                   convertEntities=BeautifulStoneSoup.HTML_ENTITIES).contents[0]
# u'Sacr\xe9 bleu!'

BeautifulSoup ne convertit pas les entités hexadécimales (& # x65;) stackoverflow.com/questions/57708/…
jfs

1
Pour BeautifulSoup4, l'équivalent serait:from bs4 import BeautifulSoup BeautifulSoup("Sacr&eacute; bl&#101;u!").contents[0]
radicand



6

Commentaire de Daniel comme réponse:

"l'échappement se produit uniquement dans Django pendant le rendu du modèle. Par conséquent, il n'y a pas besoin d'un paysage - vous dites simplement au moteur de modèle de ne pas s'échapper. soit {{context_var | safe}} ou {% autoescape off%} {{context_var}} { % endautoescape%} "


Fonctionne, sauf que ma version de Django n'a pas de «coffre-fort». J'utilise plutôt «échapper». Je suppose que c'est la même chose.
willem

1
@willem: c'est le contraire!
Asherah

5

J'ai trouvé une bonne fonction sur: 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]

L'avantage d'utiliser re est que vous pouvez faire correspondre les deux & # 039; et & # 39; en utilisant la même recherche.
Neal Stublen

Cela ne gère pas &#xA0;qui devrait décoder de la même manière que &#160;et &nbsp;.
Mike Samuel

3

Si quelqu'un cherche un moyen simple de le faire via les modèles django, vous pouvez toujours utiliser des filtres comme celui-ci:

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

J'ai eu des données provenant d'un fournisseur et tout ce que j'ai publié avait des balises html réellement écrites sur la page rendue comme si vous regardiez la source. Le code ci-dessus m'a beaucoup aidé. J'espère que cela aide les autres.

À votre santé!!


3

Même si c'est une question très ancienne, cela peut fonctionner.

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" />'

1
C'était le seul qui était capable de décoder des paires de substitution encodées en tant qu'entités html, comme "&#55349;&#56996;". Puis après un autre result.encode('utf-16', 'surrogatepass').decode('utf-16'), j'ai enfin récupéré l'original.
rescdsk

1

J'ai trouvé ça dans le code source de Cheetah ( ici )

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

Je ne sais pas pourquoi ils inversent la liste, je pense que cela a à voir avec la façon dont ils encodent, donc avec vous, cela n'a peut-être pas besoin d'être inversé. De plus, si j'étais vous, je changerais htmlCodes en une liste de tuples plutôt qu'une liste de listes ... cela se passe dans ma bibliothèque :)

J'ai remarqué que votre titre demandait également un encodage, voici donc la fonction d'encodage de 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

2
La liste est inversée car les remplacements de décodage et d'encodage doivent toujours être effectués symétriquement. Sans l'inversion, vous pourriez par exemple. convertir '& amp; lt;' en '& lt;', puis à l'étape suivante, convertissez-le incorrectement en '<'.
bobince

1

Vous pouvez également utiliser django.utils.html.escape

from django.utils.html import escape

something_nice = escape(request.POST['something_naughty'])

OP a posé la question de ne pas s'échapper, pas de s'échapper.
claymation

Dans le titre itsellf, il a également demandé l'encodage - vient de trouver votre réponse et je vous en suis reconnaissant.
Simon Steinberger

1
Pas ce que le PO a demandé, mais j'ai trouvé cela utile.
rectangletangle

0

Vous trouverez ci-dessous une fonction python qui utilise module htmlentitydefs. Ce n'est pas parfait. La version de ce htmlentitydefsque j'ai est incomplète et elle suppose que toutes les entités décodent en un point de code, ce qui est faux pour des entités comme &NotEqualTilde;:

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

NotEqualTilde;     U+02242 U+00338    ≂̸

Avec ces mises en garde cependant, voici le code.

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

0

C'est la solution la plus simple à ce problème -

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

À partir de cette page .


0

En recherchant la solution la plus simple de cette question dans Django et Python, j'ai trouvé que vous pouvez utiliser leurs fonctions intégrées pour échapper au code html / unescape.

Exemple

J'ai enregistré votre code html dans scraped_htmlet 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

Vous avez besoin de Django> = 1.0

unescape

Pour échapper à votre code html, vous pouvez utiliser django.utils.text.unescape_entities qui:

Convertissez toutes les références de caractères nommés et numériques en caractères Unicode correspondants.

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

échapper

Pour échapper à votre code html propre, vous pouvez utiliser django.utils.html.escape qui:

Renvoie le texte donné avec des esperluettes, des guillemets et des crochets angulaires codés pour une utilisation en HTML.

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

Python

Vous avez besoin de Python> = 3.4

unescape

Pour échapper à votre code html gratté, vous pouvez utiliser html.unescape qui:

Convertir toutes les références de caractères du nom et numérique (par exemple &gt;, &#62;, &x3e;) dans la chaîne s aux caractères unicode correspondants.

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

échapper

Pour échapper à votre code html propre, vous pouvez utiliser html.escape qui:

Convertir les caractères &, <et >dans la chaîne s à des séquences HTML sûres.

>>> from html import escape
>>> scraped_html == escape(clean_html)
True
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.