266 votes

Comment Stack Overflow génère-t-il ses URL favorables à l'optimisation des moteurs de recherche ?

Qu'est-ce qu'un bon produit complet ? expression régulière ou un autre processus qui prendrait le titre :

Comment modifier un titre pour qu'il fasse partie de l'URL comme Stack Overflow ?

et le transformer en

how-do-you-change-a-title-to-be-part-of-the-url-like-stack-overflow

qui est utilisé dans les URL optimisées pour le référencement sur Stack Overflow ?

L'environnement de développement que j'utilise est le suivant Ruby on Rails mais s'il existe d'autres solutions spécifiques à une plate-forme (.NET, PHP, Django ), j'aimerais bien les voir aussi.

Je suis sûr que je (ou un autre lecteur) rencontrerai le même problème sur une autre plate-forme.

J'utilise des routes personnalisées, et je voudrais surtout savoir comment modifier la chaîne pour que tous les caractères spéciaux soient supprimés, que tout soit en minuscules, et que tous les espaces blancs soient remplacés.

0 votes

Qu'en est-il des personnages amusants ? Qu'allez-vous faire à ce sujet ? Les majuscules ? La ponctuation ? Il faut en tenir compte. Fondamentalement, j'utiliserais une approche de liste blanche, par opposition à l'approche de liste noire mentionnée ci-dessus : Décrivez les caractères que vous autoriserez, ceux que vous convertirez (en quoi ?), puis remplacez le reste par quelque chose de significatif (""). Je doute que vous puissiez faire cela en une seule regex... Pourquoi ne pas simplement faire une boucle à travers les caractères ?

1 votes

Devrait être migré vers méta ; car la question et la réponse traitent toutes deux spécifiquement de la mise en œuvre de l'OS, et la réponse acceptée est celle de @JeffAtwood.

19 votes

@casperOne Pensez-vous que Jeff n'a pas le droit d'avoir une réputation non méta ? La question est de savoir "comment on peut faire quelque chose comme ça", pas spécifiquement "comment ça se fait ici".

316voto

Jeff Atwood Points 31111

Voici comment nous procédons. Notez qu'il y a probablement plus de conditions limites que vous ne le pensez à première vue.

Il s'agit de la deuxième version, déroulée pour 5 fois plus de performance (et oui, je l'ai testée). Je me suis dit que je devais l'optimiser parce que cette fonction peut être appelée des centaines de fois par page.

/// <summary>
/// Produces optional, URL-friendly version of a title, "like-this-one". 
/// hand-tuned for speed, reflects performance refactoring contributed
/// by John Gietzen (user otac0n) 
/// </summary>
public static string URLFriendly(string title)
{
    if (title == null) return "";

    const int maxlen = 80;
    int len = title.Length;
    bool prevdash = false;
    var sb = new StringBuilder(len);
    char c;

    for (int i = 0; i < len; i++)
    {
        c = title[i];
        if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))
        {
            sb.Append(c);
            prevdash = false;
        }
        else if (c >= 'A' && c <= 'Z')
        {
            // tricky way to convert to lowercase
            sb.Append((char)(c | 32));
            prevdash = false;
        }
        else if (c == ' ' || c == ',' || c == '.' || c == '/' || 
            c == '\\' || c == '-' || c == '_' || c == '=')
        {
            if (!prevdash && sb.Length > 0)
            {
                sb.Append('-');
                prevdash = true;
            }
        }
        else if ((int)c >= 128)
        {
            int prevlen = sb.Length;
            sb.Append(RemapInternationalCharToAscii(c));
            if (prevlen != sb.Length) prevdash = false;
        }
        if (i == maxlen) break;
    }

    if (prevdash)
        return sb.ToString().Substring(0, sb.Length - 1);
    else
        return sb.ToString();
}

Pour voir la version précédente du code qu'il a remplacé (mais qui est fonctionnellement équivalent, et 5x plus rapide), consultez l'historique des révisions de ce billet (cliquez sur le lien de la date).

En outre, le RemapInternationalCharToAscii Le code source de la méthode peut être trouvé aquí .

36voto

DanH Points 1702

Voici ma version du code de Jeff. J'ai apporté les modifications suivantes :

  • Les traits d'union ont été ajoutés de manière à ce qu'il soit possible d'en ajouter un, puis de le supprimer car il s'agit du dernier caractère de la chaîne. En d'autres termes, nous ne voulons jamais "my-slug-". Cela implique une allocation de chaîne supplémentaire pour le supprimer dans ce cas particulier. J'ai contourné ce problème en retardant l'ajout d'un trait d'union. Si vous comparez mon code à celui de Jeff, la logique est facile à suivre.
  • Son approche est purement basée sur la recherche et a manqué beaucoup de caractères que j'ai trouvés dans des exemples en faisant des recherches sur Stack Overflow. Pour contrer cela, j'ai d'abord effectué une passe de normalisation (AKA collation mentionnée dans la question de Meta Stack Overflow). Suppression des caractères non US-ASCII dans l'URL complète (profil) ), puis ignorer tout caractère en dehors des plages acceptables. Cela fonctionne la plupart du temps...
  • ... Lorsque ce n'est pas le cas, j'ai également dû ajouter un tableau de consultation. Comme mentionné ci-dessus, certains caractères ne correspondent pas à une valeur ASCII basse lorsqu'ils sont normalisés. Plutôt que d'abandonner ces caractères, j'ai établi une liste manuelle d'exceptions qui est sans doute pleine de lacunes, mais c'est mieux que rien. Le code de normalisation a été inspiré par l'excellent post de Jon Hanna dans Stack Overflow question Comment supprimer les accents d'une chaîne ? .
  • La conversion des cas est désormais également facultative.

    public static class Slug
    {
        public static string Create(bool toLower, params string[] values)
        {
            return Create(toLower, String.Join("-", values));
        }
    
        /// <summary>
        /// Creates a slug.
        /// References:
        /// http://www.unicode.org/reports/tr15/tr15-34.html
        /// https://meta.stackexchange.com/questions/7435/non-us-ascii-characters-dropped-from-full-profile-url/7696#7696
        /// https://stackoverflow.com/questions/25259/how-do-you-include-a-webpage-title-as-part-of-a-webpage-url/25486#25486
        /// https://stackoverflow.com/questions/3769457/how-can-i-remove-accents-on-a-string
        /// </summary>
        /// <param name="toLower"></param>
        /// <param name="normalised"></param>
        /// <returns></returns>
        public static string Create(bool toLower, string value)
        {
            if (value == null)
                return "";
    
            var normalised = value.Normalize(NormalizationForm.FormKD);
    
            const int maxlen = 80;
            int len = normalised.Length;
            bool prevDash = false;
            var sb = new StringBuilder(len);
            char c;
    
            for (int i = 0; i < len; i++)
            {
                c = normalised[i];
                if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))
                {
                    if (prevDash)
                    {
                        sb.Append('-');
                        prevDash = false;
                    }
                    sb.Append(c);
                }
                else if (c >= 'A' && c <= 'Z')
                {
                    if (prevDash)
                    {
                        sb.Append('-');
                        prevDash = false;
                    }
                    // Tricky way to convert to lowercase
                    if (toLower)
                        sb.Append((char)(c | 32));
                    else
                        sb.Append(c);
                }
                else if (c == ' ' || c == ',' || c == '.' || c == '/' || c == '\\' || c == '-' || c == '_' || c == '=')
                {
                    if (!prevDash && sb.Length > 0)
                    {
                        prevDash = true;
                    }
                }
                else
                {
                    string swap = ConvertEdgeCases(c, toLower);
    
                    if (swap != null)
                    {
                        if (prevDash)
                        {
                            sb.Append('-');
                            prevDash = false;
                        }
                        sb.Append(swap);
                    }
                }
    
                if (sb.Length == maxlen)
                    break;
            }
            return sb.ToString();
        }
    
        static string ConvertEdgeCases(char c, bool toLower)
        {
            string swap = null;
            switch (c)
            {
                case 'ı':
                    swap = "i";
                    break;
                case 'ł':
                    swap = "l";
                    break;
                case 'Ł':
                    swap = toLower ? "l" : "L";
                    break;
                case 'đ':
                    swap = "d";
                    break;
                case 'ß':
                    swap = "ss";
                    break;
                case 'ø':
                    swap = "o";
                    break;
                case 'Þ':
                    swap = "th";
                    break;
            }
            return swap;
        }
    }

Pour plus de détails, les tests unitaires, et une explication de la raison pour laquelle Facebook 's URL est un peu plus intelligent que les débordements de pile, j'ai une fonction version élargie de ce texte sur mon blog .

16voto

Dale Ragan Points 14495

Vous devrez configurer une route personnalisée pour faire pointer le fichier URL au contrôleur qui s'en chargera. Puisque vous utilisez Ruby on Rails, voici un fichier introduction dans l'utilisation de leur moteur de routage.

En Ruby, vous aurez besoin d'une expression régulière comme vous le savez déjà et voici l'expression régulière à utiliser :

def permalink_for(str)
    str.gsub(/[^\w\/]|[!\(\)\.]+/, ' ').strip.downcase.gsub(/\ +/, '-')
end

11voto

fijter Points 7671

Vous pouvez également utiliser cette JavaScript pour la génération in-form des slugs (cette fonction est basée sur/copiée à partir de Django ) :

function makeSlug(urlString, filter) {
    // Changes, e.g., "Petty theft" to "petty_theft".
    // Remove all these words from the string before URLifying

    if(filter) {
        removelist = ["a", "an", "as", "at", "before", "but", "by", "for", "from",
        "is", "in", "into", "like", "of", "off", "on", "onto", "per",
        "since", "than", "the", "this", "that", "to", "up", "via", "het", "de", "een", "en",
        "with"];
    }
    else {
        removelist = [];
    }
    s = urlString;
    r = new RegExp('\\b(' + removelist.join('|') + ')\\b', 'gi');
    s = s.replace(r, '');
    s = s.replace(/[^-\w\s]/g, ''); // Remove unneeded characters
    s = s.replace(/^\s+|\s+$/g, ''); // Trim leading/trailing spaces
    s = s.replace(/[-\s]+/g, '-'); // Convert spaces to hyphens
    s = s.toLowerCase(); // Convert to lowercase
    return s; // Trim to first num_chars characters
}

8voto

The How-To Geek Points 703

Pour faire bonne mesure, voici la fonction PHP de WordPress qui le fait... Je pense que WordPress est l'une des plateformes les plus populaires qui utilise des liens fantaisistes.

    function sanitize\_title\_with\_dashes($title) {
            $title = strip\_tags($title);
            // Preserve escaped octets.
            $title = preg\_replace('|%(\[a-fA-F0-9\]\[a-fA-F0-9\])|', '---$1---', $title);
            // Remove percent signs that are not part of an octet.
            $title = str\_replace('%', '', $title);
            // Restore octets.
            $title = preg\_replace('|---(\[a-fA-F0-9\]\[a-fA-F0-9\])---|', '%$1', $title);
            $title = remove\_accents($title);
            if (seems\_utf8($title)) {
                    if (function\_exists('mb\_strtolower')) {
                            $title = mb\_strtolower($title, 'UTF-8');
                    }
                    $title = utf8\_uri\_encode($title, 200);
            }
            $title = strtolower($title);
            $title = preg\_replace('/&.+?;/', '', $title); // kill entities
            $title = preg\_replace('/\[^%a-z0-9 \_-\]/', '', $title);
            $title = preg\_replace('/\\s+/', '-', $title);
            $title = preg\_replace('|-+|', '-', $title);
            $title = trim($title, '-');
            return $title;
    }

Cette fonction ainsi que certaines des fonctions d'appui se trouvent dans wp-includes/formatting.php.

7 votes

Cette réponse n'est pas complète. Il manque des fonctions comme : remove_accents , seems_utf8 ...

0 votes

Pour compléter la réponse de @The How-To Geek vous pouvez encore git clone git://core.git.wordpress.org/ et trouver le wp-includes/formatting.php dans le fichier

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X