Comment Stack Overflow génère-t-il ses URL optimisées pour le référencement?

253

Qu'est-ce qu'une bonne expression régulière complète 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 Ruby on Rails , mais s'il existe d'autres solutions spécifiques à la plate-forme (.NET, PHP, Django ), j'aimerais aussi les voir.

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

J'utilise des itinéraires personnalisés et je veux surtout savoir comment modifier la chaîne pour que tous les caractères spéciaux soient supprimés, tout est en minuscules et tous les espaces sont remplacés.

wusher
la source
Et les personnages drôles? Qu'allez-vous faire à ce sujet? Trémas? Ponctuation? Il faut en tenir compte. Fondamentalement, j'utiliserais une approche de liste blanche, par opposition aux approches de liste noire ci-dessus: Décrivez quels caractères vous autoriserez, quels caractères vous convertirez (en quoi?), Puis changer le reste en quelque chose de significatif ("") . Je doute que vous puissiez le faire en une seule expression régulière ... Pourquoi ne pas simplement parcourir les personnages?
Daren Thomas
1
Devrait être migré vers la méta ; car la question et la réponse traitent toutes deux spécifiquement de la mise en œuvre de SO, et la réponse acceptée vient de @JeffAtwood.
casperOne
19
@casperOne Pensez-vous que Jeff n'a pas droit à une réputation non méta? La question porte sur "comment peut-on faire quelque chose comme ça", pas spécifiquement "comment cela se fait-il ici".
Paŭlo Ebermann,
@ PaŭloEbermann: Il ne s'agit pas de donner à Jeff une réputation non méta (la réputation qu'il a n'est vraiment pas mon souci); le corps de la question faisait spécifiquement référence à l'implémentation de StackOverflow d' où la raison pour laquelle il était sur méta.
casperOne

Réponses:

300

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

Il s'agit de la deuxième version, déroulée pour 5 fois plus de performances (et oui, je l'ai testée). J'ai pensé que je l'optimiserais car 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 que celui-ci a remplacé (mais est fonctionnellement équivalent à, et 5 fois plus rapide), consultez l'historique des révisions de ce message (cliquez sur le lien de date).

En outre, le RemapInternationalCharToAsciicode source de la méthode peut être trouvé ici .

Jeff Atwood
la source
24
Ce serait bien avec une version qui ne se contente pas de laisser tomber des caractères accentués comme åäö mais plutôt de les déconcentrer en aao ... ^^
Oskar Duveborn
22
@oskar le talon de cette RemapInternationalCharToAscii()fonction est là meta.stackexchange.com/questions/7435/…
Jeff Atwood
3
C'est bien. Le seul changement que j'ai fait jusqu'à présent est de changer "if (i == maxlen) break;" devenir "if (sb.Length == maxlen) break;" juste au cas où il y aurait beaucoup de caractères invalides dans la chaîne que je passe.
Tom Chantler
4
Une optimisation mineure: if (prevdash) sb.Length -= 1; return sb.ToString();au lieu de la dernière ifdéclaration.
Mark Hurd
8
@Dommer sb.Length == maxlen break;est bogué si le signe sur maxLenght-1 est "ß" il est converti en "ss" sb.Length == maxlenene sera jamais vrai, il vaut mieux plutôt le tester (sb.Length > = maxlen).
Henrik Stenbæk
32

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

  • Les tirets ont été ajoutés de manière à ce que l'un puisse être ajouté, puis doit être supprimé car il s'agissait du dernier caractère de la chaîne. Autrement dit, nous ne voulons jamais "ma-limace-". Cela signifie une allocation de chaîne supplémentaire pour la supprimer sur ce cas de bord. J'ai travaillé autour de cela en retardant la césure. Si vous comparez mon code à celui de Jeff, la logique est facile à suivre.
  • Son approche est purement basée sur la recherche et a raté beaucoup de personnages que j'ai trouvés dans les exemples lors de recherches sur Stack Overflow. Pour contrer cela, je réalise d'abord une passe de normalisation (classement AKA mentionné dans la question Meta Stack Overflow Les caractères non US-ASCII ont été supprimés de l'URL complète (profil) ), puis j'ignore tous les caractères en dehors des plages acceptables. Cela fonctionne la plupart du temps ...
  • ... Pour le cas contraire, j'ai également dû ajouter une table de recherche. Comme mentionné ci-dessus, certains caractères ne correspondent pas à une faible valeur ASCII lorsqu'ils sont normalisés. Plutôt que de les supprimer, j'ai une liste manuelle d'exceptions qui est sans doute pleine de trous, mais c'est mieux que rien. Le code de normalisation a été inspiré par l'excellent article de Jon Hanna dans la question Stack Overflow Comment puis-je supprimer les accents sur une chaîne? .
  • La conversion de 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
        /// /meta/7435/non-us-ascii-characters-dropped-from-full-profile-url/7696#7696
        /// /programming/25259/how-do-you-include-a-webpage-title-as-part-of-a-webpage-url/25486#25486
        /// /programming/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 le schéma d' URL de Facebook est un peu plus intelligent que Stack Overflows, j'ai une version étendue de cela sur mon blog .

DanH
la source
4
+1 C'est génial Dan. J'ai également ajouté un commentaire sur votre blog sur la possibilité de changer if (i == maxlen) break;pour être à la if (sb.Length == maxlen) break;place, de sorte que si vous passez une chaîne avec beaucoup d'espaces / caractères invalides, vous pouvez toujours obtenir un slug de la longueur souhaitée, tandis que le code tel qu'il se présente pourrait finir la tronquer massivement (par exemple, considérez le cas où vous commencez avec 80 espaces ...). Et un repère approximatif de 10 000 000 d'itérations par rapport au code de Jeff a montré qu'il était à peu près à la même vitesse.
Tom Chantler
1
Merci, a répondu sur mon blog et corrigé le code là-bas et au-dessus. Merci également pour l'analyse comparative du code. Pour ceux qui étaient intéressés, c'était à égalité avec Jeff.
DanH
2
Il semble qu'il y ait des problèmes avec Slug.Create (): les versions majuscules de ÆØÅ ne sont pas correctement converties ÆØ est ignoré pendant que Å est traduit en a. Normalement, vous convertissez «å» en «aa», «ø» en «oe» et «æ» en «ae». Deuxième pause (sb.Length == maxlen); est bogué si le signe sur maxLenght-1 est "ß" (sb.Length == maxlen) ne sera jamais vrai, il vaut mieux tester plutôt (sb.Length> = maxlen). Je suis supprimé que vous coupiez sur n'importe quelle position aléatoire et que vous ne coupiez pas sur le dernier "-", cela vous évitera de terminer par un mot non recherché à la fin: comme si vous deviez couper "pour affirmer" après le dernier "s" "
Henrik Stenbæk
@DanH ce serait génial d'avoir une version javascript du code.
Freshblood
16

Vous souhaiterez configurer un itinéraire personnalisé pour pointer l' URL vers le contrôleur qui le gérera. Puisque vous utilisez Ruby on Rails, voici une introduction à l'utilisation de leur moteur de routage.

Dans 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
Dale Ragan
la source
11

Vous pouvez également utiliser cette fonction JavaScript pour la génération in-form des slugs (celle-ci est basée sur / copiée depuis 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
}
fijter
la source
L'ajout de let ou const serait génial car ce n'est pas de la vanille JS.
Aditya Anand
8

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

    fonction sanitize_title_with_dashes ($ title) {
            $ title = strip_tags ($ title);
            // Préserve les octets échappés.
            $ title = preg_replace ('|% ([a-fA-F0-9] [a-fA-F0-9]) |', '--- $ 1 ---', $ title);
            // Supprime les signes de pourcentage qui ne font pas partie d'un octet.
            $ title = str_replace ('%', '', $ title);
            // Restaurer les octets.
            $ title = preg_replace ('| --- ([a-fA-F0-9] [a-fA-F0-9]) --- |', '% $ 1', $ title);
            $ title = remove_accents ($ title);
            if (semble_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); // tuer des entités
            $ 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 de support peuvent être trouvées dans wp-includes / formating.php.

The How-To Geek
la source
6
Ce n'est pas une réponse complète. Il vous manque des fonctions telles que : remove_accents, seems_utf8...
Nikola Loncar
pour terminer @The How-To Geek, vous pouvez toujours git clone git://core.git.wordpress.org/trouver le wp-includes/formatting.phpfichier dans
mickro
5

Si vous utilisez Rails edge, vous pouvez compter sur Inflector.parametrize - voici l'exemple de la documentation:

  class Person
    def to_param
      "#{id}-#{name.parameterize}"
    end
  end

  @person = Person.find(1)
  # => #<Person id: 1, name: "Donald E. Knuth">

  <%= link_to(@person.name, person_path(@person)) %>
  # => <a href="https://stackoverflow.com/person/1-donald-e-knuth">Donald E. Knuth</a>

De plus, si vous devez gérer des caractères plus exotiques tels que les accents (éphémère) dans la version précédente de Rails, vous pouvez utiliser un mélange de PermalinkFu et DiacriticsFu :

DiacriticsFu::escape("éphémère")
=> "ephemere"

DiacriticsFu::escape("räksmörgås")
=> "raksmorgas"
Thibaut Barrère
la source
5

Je ne connais pas Ruby on Rails, mais ce qui suit est du code PHP (non testé). Vous pouvez probablement traduire cela très rapidement en Ruby on Rails si vous le trouvez utile.

$sURL = "This is a title to convert to URL-format. It has 1 number in it!";
// To lower-case
$sURL = strtolower($sURL);

// Replace all non-word characters with spaces
$sURL = preg_replace("/\W+/", " ", $sURL);

// Remove trailing spaces (so we won't end with a separator)
$sURL = trim($sURL);

// Replace spaces with separators (hyphens)
$sURL = str_replace(" ", "-", $sURL);

echo $sURL;
// outputs: this-is-a-title-to-convert-to-url-format-it-has-1-number-in-it

J'espère que ça aide.

Vegard Larsen
la source
4

Je ne parle pas beaucoup de Ruby ou de Rails, mais en Perl, voici ce que je ferais:

my $title = "How do you change a title to be part of the url like Stackoverflow?";

my $url = lc $title;   # Change to lower case and copy to URL.
$url =~ s/^\s+//g;     # Remove leading spaces.
$url =~ s/\s+$//g;     # Remove trailing spaces.
$url =~ s/\s+/\-/g;    # Change one or more spaces to single hyphen.
$url =~ s/[^\w\-]//g;  # Remove any non-word characters.

print "$title\n$url\n";

Je viens de faire un test rapide et cela semble fonctionner. Espérons que cela soit relativement facile à traduire en Ruby.

Brian
la source
4

Implémentation T-SQL, adaptée de dbo.UrlEncode :

CREATE FUNCTION dbo.Slug(@string varchar(1024))
RETURNS varchar(3072)
AS
BEGIN
    DECLARE @count int, @c char(1), @i int, @slug varchar(3072)

    SET @string = replace(lower(ltrim(rtrim(@string))),' ','-')

    SET @count = Len(@string)
    SET @i = 1
    SET @slug = ''

    WHILE (@i <= @count)
    BEGIN
        SET @c = substring(@string, @i, 1)

        IF @c LIKE '[a-z0-9--]'
            SET @slug = @slug + @c

        SET @i = @i +1
    END

    RETURN @slug
END
Sören Kuklau
la source
4

Je sais que c'est une question très ancienne, mais comme la plupart des navigateurs prennent désormais en charge les URL unicode, j'ai trouvé une excellente solution dans XRegex qui convertit tout sauf les lettres (dans toutes les langues en «-»).

Cela peut être fait dans plusieurs langages de programmation.

Le motif est \\p{^L}+et il vous suffit de l'utiliser pour remplacer toutes les lettres non par «-».

Exemple de travail dans node.js avec le module xregex .

var text = 'This ! can @ have # several $ letters % from different languages such as עברית or Español';

var slugRegEx = XRegExp('((?!\\d)\\p{^L})+', 'g');

var slug = XRegExp.replace(text, slugRegEx, '-').toLowerCase();

console.log(slug) ==> "this-can-have-several-letters-from-different-languages-such-as-עברית-or-español"
Rotem
la source
3

En supposant que votre classe de modèle possède un attribut title, vous pouvez simplement remplacer la méthode to_param dans le modèle, comme ceci:

def to_param
  title.downcase.gsub(/ /, '-')
end

Cet épisode de Railscast a tous les détails. Vous pouvez également vous assurer que le titre ne contient que des caractères valides en utilisant ceci:

validates_format_of :title, :with => /^[a-z0-9-]+$/,
                    :message => 'can only contain letters, numbers and hyphens'
John Topley
la source
2

Le code de Brian, en Ruby:

title.downcase.strip.gsub(/\ /, '-').gsub(/[^\w\-]/, '')

downcasetransforme la chaîne en minuscules, stripsupprime les espaces avant et, le premier gsubappel g lobally sous espaces stitutes avec des tirets, et le second supprime tout ce qui est pas une lettre ou un tiret.

Sören Kuklau
la source
2

Il y a un petit plugin Ruby on Rails appelé PermalinkFu , qui fait cela. La méthode d'échappement effectue la transformation en une chaîne adaptée à une URL . Jetez un œil au code; cette méthode est assez simple.

Pour supprimer les caractères non ASCII , il utilise la bibliothèque iconv pour traduire en 'ascii // ignore // translit' de 'utf-8'. Les espaces sont ensuite transformés en tirets, tout est déclassé, etc.

Lau
la source
Bien que cela fonctionne parfaitement, je pense que ce n'est pas très efficace.
WhyNotHugo
2

Vous pouvez utiliser la méthode d'assistance suivante. Il peut convertir les caractères Unicode.

public static string ConvertTextToSlug(string s)
{
    StringBuilder sb = new StringBuilder();

    bool wasHyphen = true;

    foreach (char c in s)
    {
        if (char.IsLetterOrDigit(c))
        {
            sb.Append(char.ToLower(c));
            wasHyphen = false;
        }
        else
            if (char.IsWhiteSpace(c) && !wasHyphen)
            {
                sb.Append('-');
                wasHyphen = true;
            }
    }

    // Avoid trailing hyphens
    if (wasHyphen && sb.Length > 0)
        sb.Length--;

    return sb.ToString().Replace("--","-");
}
Peyman Mehrabani
la source
2

Voici ma version (plus lente, mais amusante à écrire) du code de Jeff:

public static string URLFriendly(string title)
{
    char? prevRead = null,
        prevWritten = null;

    var seq = 
        from c in title
        let norm = RemapInternationalCharToAscii(char.ToLowerInvariant(c).ToString())[0]
        let keep = char.IsLetterOrDigit(norm)
        where prevRead.HasValue || keep
        let replaced = keep ? norm
            :  prevWritten != '-' ? '-'
            :  (char?)null
        where replaced != null
        let s = replaced + (prevRead == null ? ""
            : norm == '#' && "cf".Contains(prevRead.Value) ? "sharp"
            : norm == '+' ? "plus"
            : "")
        let _ = prevRead = norm
        from written in s
        let __ = prevWritten = written
        select written;

    const int maxlen = 80;  
    return string.Concat(seq.Take(maxlen)).TrimEnd('-');
}

public static string RemapInternationalCharToAscii(string text)
{
    var seq = text.Normalize(NormalizationForm.FormD)
        .Where(c => CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark);

    return string.Concat(seq).Normalize(NormalizationForm.FormC);
}

Ma chaîne de test:

" I love C#, F#, C++, and... Crème brûlée!!! They see me codin'... they hatin'... tryin' to catch me codin' dirty... "

Ronnie Overby
la source
2

La solution stackoverflow est excellente, mais un navigateur moderne (à l'exception d'IE, comme d'habitude) gère désormais bien l'encodage utf8:

entrez la description de l'image ici

J'ai donc mis à jour la solution proposée:

public static string ToFriendlyUrl(string title, bool useUTF8Encoding = false)
{
    ...

        else if (c >= 128)
        {
            int prevlen = sb.Length;
            if (useUTF8Encoding )
            {
                sb.Append(HttpUtility.UrlEncode(c.ToString(CultureInfo.InvariantCulture),Encoding.UTF8));
            }
            else
            {
                sb.Append(RemapInternationalCharToAscii(c));
            }
    ...
}

Code complet sur Pastebin

Edit: voici le code de la RemapInternationalCharToAsciiméthode (qui manque dans la boîte à pâte).

giammin
la source
Selon Wikipedia , Mozilla 1.4, Netscape 7.1, Opera 7.11 ont été parmi les premières applications à prendre en charge IDNA. Un plug-in de navigateur est disponible pour Internet Explorer 6 pour fournir un support IDN. Internet Explorer 7.0 et les API URL de Windows Vista fournissent une prise en charge native de l'IDN. Cela ressemble à la suppression de caractères UTF-8 est une perte de temps. Vive UTF-8 !!!
Muhammad Rehan Saeed du
1

J'ai aimé la façon dont cela se fait sans utiliser d' expressions régulières , donc je l'ai porté sur PHP. Je viens d'ajouter une fonction appelée is_betweenpour vérifier les caractères:

function is_between($val, $min, $max)
{
    $val = (int) $val; $min = (int) $min; $max = (int) $max;

    return ($val >= $min && $val <= $max);
}

function international_char_to_ascii($char)
{
    if (mb_strpos('àåáâäãåa', $char) !== false)
    {
        return 'a';
    }

    if (mb_strpos('èéêëe', $char) !== false)
    {
        return 'e';
    }

    if (mb_strpos('ìíîïi', $char) !== false)
    {
        return 'i';
    }

    if (mb_strpos('òóôõö', $char) !== false)
    {
        return 'o';
    }

    if (mb_strpos('ùúûüuu', $char) !== false)
    {
        return 'u';
    }

    if (mb_strpos('çccc', $char) !== false)
    {
        return 'c';
    }

    if (mb_strpos('zzž', $char) !== false)
    {
        return 'z';
    }

    if (mb_strpos('ssšs', $char) !== false)
    {
        return 's';
    }

    if (mb_strpos('ñn', $char) !== false)
    {
        return 'n';
    }

    if (mb_strpos('ýÿ', $char) !== false)
    {
        return 'y';
    }

    if (mb_strpos('gg', $char) !== false)
    {
        return 'g';
    }

    if (mb_strpos('r', $char) !== false)
    {
        return 'r';
    }

    if (mb_strpos('l', $char) !== false)
    {
        return 'l';
    }

    if (mb_strpos('d', $char) !== false)
    {
        return 'd';
    }

    if (mb_strpos('ß', $char) !== false)
    {
        return 'ss';
    }

    if (mb_strpos('Þ', $char) !== false)
    {
        return 'th';
    }

    if (mb_strpos('h', $char) !== false)
    {
        return 'h';
    }

    if (mb_strpos('j', $char) !== false)
    {
        return 'j';
    }
    return '';
}

function url_friendly_title($url_title)
{
    if (empty($url_title))
    {
        return '';
    }

    $url_title = mb_strtolower($url_title);

    $url_title_max_length   = 80;
    $url_title_length       = mb_strlen($url_title);
    $url_title_friendly     = '';
    $url_title_dash_added   = false;
    $url_title_char = '';

    for ($i = 0; $i < $url_title_length; $i++)
    {
        $url_title_char     = mb_substr($url_title, $i, 1);

        if (strlen($url_title_char) == 2)
        {
            $url_title_ascii    = ord($url_title_char[0]) * 256 + ord($url_title_char[1]) . "\r\n";
        }
        else
        {
            $url_title_ascii    = ord($url_title_char);
        }

        if (is_between($url_title_ascii, 97, 122) || is_between($url_title_ascii, 48, 57))
        {
            $url_title_friendly .= $url_title_char;

            $url_title_dash_added = false;
        }
        elseif(is_between($url_title_ascii, 65, 90))
        {
            $url_title_friendly .= chr(($url_title_ascii | 32));

            $url_title_dash_added = false;
        }
        elseif($url_title_ascii == 32 || $url_title_ascii == 44 || $url_title_ascii == 46 || $url_title_ascii == 47 || $url_title_ascii == 92 || $url_title_ascii == 45 || $url_title_ascii == 47 || $url_title_ascii == 95 || $url_title_ascii == 61)
        {
            if (!$url_title_dash_added && mb_strlen($url_title_friendly) > 0)
            {
                $url_title_friendly .= chr(45);

                $url_title_dash_added = true;
            }
        }
        else if ($url_title_ascii >= 128)
        {
            $url_title_previous_length = mb_strlen($url_title_friendly);

            $url_title_friendly .= international_char_to_ascii($url_title_char);

            if ($url_title_previous_length != mb_strlen($url_title_friendly))
            {
                $url_title_dash_added = false;
            }
        }

        if ($i == $url_title_max_length)
        {
            break;
        }
    }

    if ($url_title_dash_added)
    {
        return mb_substr($url_title_friendly, 0, -1);
    }
    else
    {
        return $url_title_friendly;
    }
}
Peter Mortensen
la source
1

Désormais, tous les navigateurs gèrent bien l'encodage utf8, vous pouvez donc utiliser la méthode WebUtility.UrlEncode , comme HttpUtility.UrlEncode, utilisée par @giamin, mais son travail en dehors d'une application Web.

ikourfaln
la source
1

J'ai porté le code sur TypeScript. Il peut facilement être adapté à JavaScript.

J'ajoute une .containsméthode au Stringprototype, si vous ciblez les derniers navigateurs ou ES6, vous pouvez utiliser à la .includesplace.

if (!String.prototype.contains) {
    String.prototype.contains = function (check) {
        return this.indexOf(check, 0) !== -1;
    };
}

declare interface String {
    contains(check: string): boolean;
}

export function MakeUrlFriendly(title: string) {
            if (title == null || title == '')
                return '';

            const maxlen = 80;
            let len = title.length;
            let prevdash = false;
            let result = '';
            let c: string;
            let cc: number;
            let remapInternationalCharToAscii = function (c: string) {
                let s = c.toLowerCase();
                if ("àåáâäãåą".contains(s)) {
                    return "a";
                }
                else if ("èéêëę".contains(s)) {
                    return "e";
                }
                else if ("ìíîïı".contains(s)) {
                    return "i";
                }
                else if ("òóôõöøőð".contains(s)) {
                    return "o";
                }
                else if ("ùúûüŭů".contains(s)) {
                    return "u";
                }
                else if ("çćčĉ".contains(s)) {
                    return "c";
                }
                else if ("żźž".contains(s)) {
                    return "z";
                }
                else if ("śşšŝ".contains(s)) {
                    return "s";
                }
                else if ("ñń".contains(s)) {
                    return "n";
                }
                else if ("ýÿ".contains(s)) {
                    return "y";
                }
                else if ("ğĝ".contains(s)) {
                    return "g";
                }
                else if (c == 'ř') {
                    return "r";
                }
                else if (c == 'ł') {
                    return "l";
                }
                else if (c == 'đ') {
                    return "d";
                }
                else if (c == 'ß') {
                    return "ss";
                }
                else if (c == 'Þ') {
                    return "th";
                }
                else if (c == 'ĥ') {
                    return "h";
                }
                else if (c == 'ĵ') {
                    return "j";
                }
                else {
                    return "";
                }
            };

            for (let i = 0; i < len; i++) {
                c = title[i];
                cc = c.charCodeAt(0);

                if ((cc >= 97 /* a */ && cc <= 122 /* z */) || (cc >= 48 /* 0 */ && cc <= 57 /* 9 */)) {
                    result += c;
                    prevdash = false;
                }
                else if ((cc >= 65 && cc <= 90 /* A - Z */)) {
                    result += c.toLowerCase();
                    prevdash = false;
                }
                else if (c == ' ' || c == ',' || c == '.' || c == '/' || c == '\\' || c == '-' || c == '_' || c == '=') {
                    if (!prevdash && result.length > 0) {
                        result += '-';
                        prevdash = true;
                    }
                }
                else if (cc >= 128) {
                    let prevlen = result.length;
                    result += remapInternationalCharToAscii(c);
                    if (prevlen != result.length) prevdash = false;
                }
                if (i == maxlen) break;
            }

            if (prevdash)
                return result.substring(0, result.length - 1);
            else
                return result;
        }
Sam
la source
0

Non non Non. Vous vous trompez tous tellement. Sauf pour les trucs diacritiques-fu, vous y arrivez, mais qu'en est-il des personnages asiatiques (dommage aux développeurs Ruby de ne pas considérer leur nihonjin frères ).

Firefox et Safari affichent tous deux des caractères non ASCII dans l' URL , et franchement, ils ont fière allure. Il est agréable de prendre en charge des liens comme « http://somewhere.com/news/read/ お 前 た ち は ア ホ じ ゃ な い か い ».

Voici donc du code PHP qui le fera, mais je viens de l'écrire et je ne l'ai pas testé.

<?php
    function slug($str)
    {
        $args = func_get_args();
        array_filter($args);  //remove blanks
        $slug = mb_strtolower(implode('-', $args));

        $real_slug = '';
        $hyphen = '';
        foreach(SU::mb_str_split($slug) as $c)
        {
            if (strlen($c) > 1 && mb_strlen($c)===1)
            {
                $real_slug .= $hyphen . $c;
                $hyphen = '';
            }
            else
            {
                switch($c)
                {
                    case '&':
                        $hyphen = $real_slug ? '-and-' : '';
                        break;
                    case 'a':
                    case 'b':
                    case 'c':
                    case 'd':
                    case 'e':
                    case 'f':
                    case 'g':
                    case 'h':
                    case 'i':
                    case 'j':
                    case 'k':
                    case 'l':
                    case 'm':
                    case 'n':
                    case 'o':
                    case 'p':
                    case 'q':
                    case 'r':
                    case 's':
                    case 't':
                    case 'u':
                    case 'v':
                    case 'w':
                    case 'x':
                    case 'y':
                    case 'z':

                    case 'A':
                    case 'B':
                    case 'C':
                    case 'D':
                    case 'E':
                    case 'F':
                    case 'G':
                    case 'H':
                    case 'I':
                    case 'J':
                    case 'K':
                    case 'L':
                    case 'M':
                    case 'N':
                    case 'O':
                    case 'P':
                    case 'Q':
                    case 'R':
                    case 'S':
                    case 'T':
                    case 'U':
                    case 'V':
                    case 'W':
                    case 'X':
                    case 'Y':
                    case 'Z':

                    case '0':
                    case '1':
                    case '2':
                    case '3':
                    case '4':
                    case '5':
                    case '6':
                    case '7':
                    case '8':
                    case '9':
                        $real_slug .= $hyphen . $c;
                        $hyphen = '';
                        break;

                    default:
                       $hyphen = $hyphen ? $hyphen : ($real_slug ? '-' : '');
                }
            }
        }
        return $real_slug;
    }

Exemple:

$str = "~!@#$%^&*()_+-=[]\{}|;':\",./<>?\n\r\t\x07\x00\x04 コリン ~!@#$%^&*()_+-=[]\{}|;':\",./<>?\n\r\t\x07\x00\x04 トーマス ~!@#$%^&*()_+-=[]\{}|;':\",./<>?\n\r\t\x07\x00\x04 アーノルド ~!@#$%^&*()_+-=[]\{}|;':\",./<>?\n\r\t\x07\x00\x04";
echo slug($str);

Sorties: コ リ ン -et- ト ー マ ス -et- ア ー ノ ル ド

Le «-et-» est dû au fait que & «est changé en« -et- ».

Peter Mortensen
la source
4
Je ne sais vraiment pas quoi dire de cette information.
sjas
3
C'est un très bon exemple de cas où NE PAS utiliser une instruction switch case.
NickG