Slugification de chaîne en Python

92

Je suis à la recherche du meilleur moyen de "slugifier" la chaîne de ce qu'est "slug" , et ma solution actuelle est basée sur cette recette

Je l'ai un peu changé pour:

s = 'String to slugify'

slug = unicodedata.normalize('NFKD', s)
slug = slug.encode('ascii', 'ignore').lower()
slug = re.sub(r'[^a-z0-9]+', '-', slug).strip('-')
slug = re.sub(r'[-]+', '-', slug)

Quelqu'un voit-il des problèmes avec ce code? Cela fonctionne bien, mais peut-être que je manque quelque chose ou que vous connaissez une meilleure façon?

Zygimantas
la source
travaillez-vous beaucoup avec unicode? si c'est le cas, le dernier re.sub pourrait être meilleur si vous enroulez unicode () autour de lui, c'est ce que fait django. De plus, [^ a-z0-9] + peut être raccourci pour utiliser \ w. voir django.template.defaultfilters, il est proche du vôtre, mais un peu plus raffiné.
Mike Ramirez
Les caractères Unicode sont-ils autorisés dans l'URL? De plus, j'ai changé \ w en a-z0-9 parce que \ w inclut le caractère _ et les lettres majuscules. Les lettres sont définies en minuscules à l'avance, il n'y aura donc pas de lettres majuscules à faire correspondre.
Zygimantas
'_' est valide (mais votre choix, vous l'avez demandé), unicode est en pourcentage de caractères encodés.
Mike Ramirez
Merci Mike. Eh bien, j'ai posé une mauvaise question. Y a-t-il une raison de le coder en chaîne unicode, si nous avons déjà remplacé tous les caractères sauf "az", "0-9" et "-"?
Zygimantas
Pour django, je pense qu'il est important pour eux d'avoir toutes les chaînes en tant qu'objets Unicode pour des raisons de compatibilité. C'est votre choix si vous le souhaitez.
Mike Ramirez

Réponses:

144

Il existe un package python nommé python-slugify, qui fait un très bon travail de slugification:

pip install python-slugify

Fonctionne comme ceci:

from slugify import slugify

txt = "This is a test ---"
r = slugify(txt)
self.assertEquals(r, "this-is-a-test")

txt = "This -- is a ## test ---"
r = slugify(txt)
self.assertEquals(r, "this-is-a-test")

txt = 'C\'est déjà l\'été.'
r = slugify(txt)
self.assertEquals(r, "cest-deja-lete")

txt = 'Nín hǎo. Wǒ shì zhōng guó rén'
r = slugify(txt)
self.assertEquals(r, "nin-hao-wo-shi-zhong-guo-ren")

txt = 'Компьютер'
r = slugify(txt)
self.assertEquals(r, "kompiuter")

txt = 'jaja---lol-méméméoo--a'
r = slugify(txt)
self.assertEquals(r, "jaja-lol-mememeoo-a")

Voir plus d'exemples

Ce paquet fait un peu plus que ce que vous avez publié (jetez un œil à la source, ce n'est qu'un fichier). Le projet est toujours actif (a été mis à jour 2 jours avant ma réponse initiale, plus de sept ans plus tard (dernière vérification le 30/06/2020), il est toujours mis à jour).

attention : il y a un deuxième paquet autour, nommé slugify. Si vous possédez les deux, vous risquez de rencontrer un problème, car ils portent le même nom pour l'importation. Celui que je viens de nommer slugifyn'a pas fait tout ce que j'ai vérifié rapidement: "Ich heiße"est devenu "ich-heie"(devrait être "ich-heisse"), alors assurez-vous de choisir le bon, lorsque vous utilisez pipou easy_install.

Kratenko
la source
6
python-slugifyest sous licence MIT, mais il utilise Unidecodece qui est sous licence GPL, il peut donc ne pas convenir à certains projets.
Rotareti
@Rotareti Pourriez-vous s'il vous plaît m'expliquer pourquoi il ne convient pas à tous les projets? Ne pouvons-nous pas utiliser quoi que ce soit sous licence MIT ou GPL et les inclure dans un logiciel commercial? Je pense que la seule restriction est de mettre la licence en plus des codes que nous développons. Ai-je tort?
Ghassem Tofighi
1
@GhassemTofighi En bref: vous pouvez l'utiliser dans votre logiciel commercial, mais si vous l'utilisez, vous devez également ouvrir votre code. Quoi qu'il en soit IANAL et ce n'est pas un avis juridique.
Rotareti
@GhassemTofighi peut-être jeter un oeil à softwareengineering.stackexchange.com/q/47032/71504 sur ce sujet
kratenko
1
@Rotareti utilise python-slugifydésormais par défaut la licence artistique au text-unidecodelieu de la licence GPL Unidecode, ce qui répond à votre problème de licence. github.com/un33k/python-slugify/commit/…
Emilien
31

Installez le formulaire Unidecode à partir d'ici pour le support Unicode

pip installer unidecode

# -*- coding: utf-8 -*-
import re
import unidecode

def slugify(text):
    text = unidecode.unidecode(text).lower()
    return re.sub(r'[\W_]+', '-', text)

text = u"My custom хелло ворлд"
print slugify(text)

>>> mon-personnalisé-khello-vorld

user1078810
la source
1
salut, c'est un peu étrange mais ça donne pour mes res comme ça "my-custom-ndud-d-d3-4-d2d3-4nd-d-"
derevo
1
@derevo qui se produit lorsque vous n'envoyez pas de chaînes Unicode. Remplacez slugify("My custom хелло ворлд")par slugify(u"My custom хелло ворлд"), et cela devrait fonctionner.
kratenko
9
Je suggérerais de ne pas utiliser de noms de variables comme str. Cela masque le strtype intégré .
crodjer
2
unidecode est GPL, ce qui peut ne pas convenir à certains.
Jorge Leitao
Qu'en est-il du reslugifying ou deslugifying.
Ryan Chou
11

Il existe un package python nommé awesome-slugify :

pip install awesome-slugify

Fonctionne comme ceci:

from slugify import slugify

slugify('one kožušček')  # one-kozuscek

page github génial-slugify

voronine
la source
2
Bon paquet! Mais attention, il est sous licence GPL.
Rotareti
1
Attention: cela ne diminuera pas automatiquement () vos URL. Vous devrez courir slugify(text).lower()si vous le souhaitez.
Kalob Taulien
6

Cela fonctionne bien dans Django , donc je ne vois pas pourquoi ce ne serait pas une bonne fonction slugify à usage général.

Avez-vous des problèmes avec cela?

Nick Presta
la source
Il est possible que dans certains cas, ce soit une bonne dose de paranoïa :-)
nemesisfixx
Le code a déménagé ici .
raylu
12
Pour les fainéants:from django.utils.text import slugify
Spartacus
6

Le problème vient de la ligne de normalisation ascii:

slug = unicodedata.normalize('NFKD', s)

C'est ce qu'on appelle la normalisation unicode qui ne décompose pas beaucoup de caractères en ascii. Par exemple, cela supprimerait les caractères non-ascii des chaînes suivantes:

Mørdag -> mrdag
Æther -> ther

Une meilleure façon de le faire est d'utiliser le module unidecode qui essaie de translittérer les chaînes en ascii. Donc, si vous remplacez la ligne ci-dessus par:

import unidecode
slug = unidecode.unidecode(s)

Vous obtenez de meilleurs résultats pour les chaînes ci-dessus et pour de nombreux caractères grecs et russes également:

Mørdag -> mordag
Æther -> aether
Björn Lindqvist
la source
5
def slugify(value):
    """
    Converts to lowercase, removes non-word characters (alphanumerics and
    underscores) and converts spaces to hyphens. Also strips leading and
    trailing whitespace.
    """
    value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore').decode('ascii')
    value = re.sub('[^\w\s-]', '', value).strip().lower()
    return mark_safe(re.sub('[-\s]+', '-', value))
slugify = allow_lazy(slugify, six.text_type)

C'est la fonction slugify présente dans django.utils.text Cela devrait suffire à vos besoins.

Animesh Sharma
la source
3

Unidecode est bon; cependant, soyez prudent: unidecode est GPL. Si cette licence ne convient pas, utilisez celle-ci

Mikhail Korobov
la source
2

Quelques options sur GitHub:

  1. https://github.com/dimka665/awesome-slugify
  2. https://github.com/un33k/python-slugify
  3. https://github.com/mozilla/unicode-slugify

Chacun prend en charge des paramètres légèrement différents pour son API, vous devrez donc examiner ce que vous préférez.

En particulier, faites attention aux différentes options qu'ils offrent pour traiter les caractères non ASCII. Pydanny a écrit un article de blog très utile illustrant certaines des différences de gestion Unicode dans ces bibliothèques slugify'ing: http://www.pydanny.com/awesome-slugify-human-readable-url-slugs-from-any-string.html Ce billet de blog est légèrement obsolète car celui de Mozilla unicode-slugifyn'est plus spécifique à Django.

Notez également qu'il awesome-slugifys'agit actuellement de GPLv3, bien qu'il y ait un problème ouvert où l'auteur dit qu'il préférerait publier en tant que MIT / BSD, mais pas sûr de la légalité: https://github.com/dimka665/awesome-slugify/issues/ 24

Jeff Widman
la source
1

Vous pouvez envisager de remplacer la dernière ligne par

slug=re.sub(r'--+',r'-',slug)

puisque le modèle [-]+n'est pas différent de -+, et que vous ne vous souciez pas vraiment de faire correspondre un seul trait d'union, seulement deux ou plus.

Mais, bien sûr, c'est assez mineur.

unutbu
la source
0

Une autre option est boltons.strutils.slugify. Boltons a également quelques autres fonctions utiles et est distribué sous BSDlicence.

ostrokach
la source