In che modo Stack Overflow genera i suoi URL SEO-friendly?


253

Qual è una buona espressione regolare completa o qualche altro processo che prenderebbe il titolo:

Come si modifica un titolo per far parte dell'URL come Stack Overflow?

e trasformalo in

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

che viene utilizzato negli URL SEO-friendly su Stack Overflow?

L'ambiente di sviluppo che sto usando è Ruby on Rails , ma se ci sono altre soluzioni specifiche per la piattaforma (.NET, PHP, Django ), mi piacerebbe vederle anche quelle.

Sono sicuro che io (o un altro lettore) incontreremo lo stesso problema su una piattaforma diversa lungo la linea.

Sto usando percorsi personalizzati e desidero principalmente sapere come modificare la stringa per rimuovere tutti i caratteri speciali, è tutto in minuscolo e tutto lo spazio bianco è sostituito.


E i personaggi divertenti? Che cosa hai intenzione di fare di quelli? Le dieresi? Punteggiatura? Questi devono essere considerati. Fondamentalmente, userei un approccio di lista bianca, al contrario degli approcci di lista nera sopra: Descrivi quali caratteri consentirai, quali caratteri convertirai (in cosa?) E poi cambierei il resto in qualcosa di significativo ("") . Dubito che tu possa farlo in una sola regex ... Perché non scorrere semplicemente i personaggi?
Daren Thomas,

1
Dovrebbe essere migrato a meta ; poiché la domanda e la risposta trattano entrambe in modo specifico dell'implementazione SO e la risposta accettata proviene da @JeffAtwood.
casperIl

19
@casperOne Pensi che a Jeff non sia concessa una qualche non reputazione? La domanda riguarda "come si può fare qualcosa del genere", non specificamente "come si fa qui".
Paŭlo Ebermann,

@ PaŭloEbermann: Non si tratta di Jeff ottenere qualche reputazione non meta (la quanta reputazione che ha non è davvero la mia preoccupazione); il corpo della domanda ha fatto specifico riferimento all'implementazione di StackOverflow, quindi la logica per cui si trova su meta.
casperUn

Risposte:


300

Ecco come lo facciamo. Si noti che probabilmente ci sono più condizioni del bordo di quanto si pensi a prima vista.

Questa è la seconda versione, srotolata per prestazioni 5 volte superiori (e sì, l'ho confrontata). Ho pensato di ottimizzarlo perché questa funzione può essere chiamata centinaia di volte per pagina.

/// <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();
}

Per vedere la versione precedente del codice che è stata sostituita (ma è funzionalmente equivalente e 5 volte più veloce), visualizzare la cronologia delle revisioni di questo post (fare clic sul collegamento della data).

Anche il RemapInternationalCharToAscii codice sorgente metodo è disponibile qui .


24
Sarebbe bello con una versione che non lasciasse solo personaggi accentati come åäö ma li disassociava in aao ... ^^
Oskar Duveborn,

22
@oskar lo stub di quella RemapInternationalCharToAscii()funzione è lì meta.stackexchange.com/questions/7435/…
Jeff Atwood

3
Questo è fantastico L'unica modifica che ho apportato finora è cambiare "if (i == maxlen) break;" diventare "if (sb.Length == maxlen) break;" nel caso in cui ci siano molti caratteri non validi nella stringa che sto passando.
Tom Chantler,

4
Un'ottimizzazione minore: if (prevdash) sb.Length -= 1; return sb.ToString();invece dell'ultima ifaffermazione.
Mark Hurd,

8
@Dommer sb.Length == maxlen break;è difettoso se il segno su maxLenght-1 è "ß" che viene convertito in "ss" sb.Length == maxlenenon sarà mai vero, è meglio invece provare (sb.Length > = maxlen).
Henrik Stenbæk,

32

Ecco la mia versione del codice di Jeff. Ho apportato le seguenti modifiche:

  • I trattini sono stati aggiunti in modo tale da poter essere aggiunti e quindi è necessario rimuoverli poiché era l'ultimo carattere della stringa. Cioè, non vogliamo mai "my-slug-". Ciò significa un'allocazione di stringa aggiuntiva per rimuoverla in questo caso limite. Ho aggirato il problema ritardando il trattino. Se confronti il ​​mio codice con quello di Jeff, la logica è facile da seguire.
  • Il suo approccio è basato esclusivamente sulla ricerca e ha perso molti personaggi che ho trovato negli esempi durante le ricerche su Stack Overflow. Per contrastare ciò, per prima cosa eseguo un passaggio di normalizzazione (regole di confronto AKA menzionate nella domanda Overflow Meta Stack I caratteri non US-ASCII rilasciati dall'URL completo (profilo) ), quindi ignoro tutti i caratteri al di fuori degli intervalli accettabili. Funziona la maggior parte del tempo ...
  • ... Per quando no, ho anche dovuto aggiungere una tabella di ricerca. Come accennato in precedenza, alcuni caratteri non vengono associati a un valore ASCII basso quando normalizzati. Invece di rilasciarli, ho un elenco manuale di eccezioni che è senza dubbio pieno di buchi, ma è meglio di niente. Il codice di normalizzazione è stato ispirato dal grande post di Jon Hanna nella domanda Stack Overflow Come posso rimuovere gli accenti su una stringa? .
  • La conversione del caso è ora anche facoltativa.

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

Per maggiori dettagli, i test di unità, e una spiegazione del motivo per cui Facebook 's URL schema è un po' più intelligente di Stack Overflow, ho un versione ampliata di questo sul mio blog .


4
+1 Questo è fantastico Dan. Ho anche aggiunto un commento sul tuo blog riguardo alla possibilità di cambiare if (i == maxlen) break;per essere if (sb.Length == maxlen) break;invece in modo che se si passa in una stringa con un sacco di spazi / caratteri non validi è ancora possibile ottenere una lumaca della lunghezza desiderata, mentre il codice così com'è potrebbe finire troncandolo in modo massiccio (ad esempio, considera il caso in cui inizi con 80 spazi ...). E un benchmark approssimativo di 10.000.000 di iterazioni contro il codice di Jeff ha mostrato che è all'incirca la stessa velocità.
Tom Chantler,

1
Grazie, ha risposto sul mio blog e ha corretto il codice lì e sopra. Grazie anche per il benchmarking del codice. Per gli interessati era alla pari di Jeff.
Dan,

2
Sembra che ci siano alcuni problemi con Slug.Create (): le versioni maiuscole di ÆØÅ non vengono convertite correttamente ÆØ viene ignorato mentre Å viene tradotto in a. Normalmente convertirai "å" in "aa", "ø" in "oe" e "æ" in "ae". Seconda interruzione (sb.Length == maxlen); è difettoso se il segno su maxLenght-1 è "ß" (sb.Length == maxlen) non sarà mai vero, è meglio invece provare per (sb.Length> = maxlen). Sono soppresso che tu tagli su qualsiasi posizione casuale e non tagli sull'ultima "-", questo ti salverà dal finire con una parola non desiderata alla fine: come se dovessi tagliare "per affermare" dopo le ultime "s "
Henrik Stenbæk,

@DanH sarebbe bello avere una versione javascript del codice.
Freshblood il

16

Dovrai impostare un percorso personalizzato per puntare l' URL al controller che lo gestirà. Dato che stai usando Ruby on Rails, ecco un'introduzione all'uso del loro motore di routing.

In Ruby, avrai bisogno di un'espressione regolare come già sai ed ecco l'espressione regolare da usare:

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

11

Puoi anche usare questa funzione JavaScript per la generazione in forma di slug (questa è basata su / copiata da 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
}

L'aggiunta di alcuni Let's o const sarebbe fantastico in quanto questo non è JS vaniglia.
Aditya Anand,

8

Per una buona misura, ecco la funzione PHP in WordPress che lo fa ... Penso che WordPress sia una delle piattaforme più popolari che utilizza collegamenti fantasiosi.

    funzione sanitize_title_with_dashes ($ title) {
            $ title = strip_tags ($ title);
            // Conserva gli ottetti fuggiti.
            $ title = preg_replace ('|% ([a-fA-F0-9] [a-fA-F0-9]) |', '--- $ 1 ---', $ title);
            // Rimuovi i segni di percentuale che non fanno parte di un ottetto.
            $ title = str_replace ('%', '', $ title);
            // Ripristina ottetti.
            $ title = preg_replace ('| --- ([a-fA-F0-9] [a-fA-F0-9]) --- |', '% $ 1', $ title);
            $ title = remove_accents ($ title);
            if (seem_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); // uccide le entità
            $ title = preg_replace ('/ [^% a-z0-9 _-] /', '', $ title);
            $ title = preg_replace ('/ \ s + /', '-', $ title);
            $ title = preg_replace ('| - + |', '-', $ title);
            $ title = trim ($ title, '-');
            restituisce $ title;
    }

Questa funzione e alcune delle funzioni di supporto sono disponibili in wp-Includes / formatting.php.


6
Questa non è una risposta completa. Si sono funzioni mancanti come: remove_accents, seems_utf8...
Nikola Loncar

per completare la risposta di How-To Geek è ancora possibile git clone git://core.git.wordpress.org/e trovare il wp-includes/formatting.phpfile in
mickro

5

Se stai usando Rails edge, puoi fare affidamento su Inflector.parametrize - ecco l'esempio dalla documentazione:

  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>

Inoltre, se devi gestire personaggi più esotici come gli accenti (éphémère) nella versione precedente di Rails, puoi usare una miscela di PermalinkFu e DiacriticsFu :

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

DiacriticsFu::escape("räksmörgås")
=> "raksmorgas"

5

Non ho familiarità con Ruby on Rails, ma il seguente è il codice PHP (non testato). Probabilmente puoi tradurlo molto rapidamente in Ruby on Rails se lo trovi 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

Spero che aiuti.


4

Non ho molto a che fare con Ruby o Rails, ma in Perl, questo è quello che farei:

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

Ho appena fatto un test rapido e sembra funzionare. Spero che questo sia relativamente facile da tradurre in Ruby.


4

Implementazione di T-SQL, adattata da 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

4

So che è una domanda molto vecchia, ma poiché la maggior parte dei browser ora supporta gli URL Unicode ho trovato un'ottima soluzione in XRegex che converte tutto tranne le lettere (in tutte le lingue in '-').

Questo può essere fatto in diversi linguaggi di programmazione.

Il modello è \\p{^L}+e quindi devi solo usarlo per sostituire tutte le non lettere in '-'.

Esempio funzionante in node.js con modulo 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"

3

Supponendo che la tua classe di modello abbia un attributo title, puoi semplicemente sovrascrivere il metodo to_param all'interno del modello, in questo modo:

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

Questo episodio di Railscast ha tutti i dettagli. Puoi anche assicurarti che il titolo contenga solo caratteri validi usando questo:

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

2

Il codice di Brian, in Ruby:

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

downcasetrasforma la stringa in minuscolo, striprimuove spazi iniziali e finali, la prima gsubchiamata g lobally sub stitutes spazi con trattini, e la seconda rimuove tutto ciò che non è una lettera o un trattino.


2

C'è un piccolo plugin di Ruby on Rails chiamato PermalinkFu , che fa questo. Il metodo escape esegue la trasformazione in una stringa adatta per un URL . Dai un'occhiata al codice; quel metodo è abbastanza semplice.

Per rimuovere caratteri non ASCII , usa iconv lib per tradurre in 'ascii // ignore // translit' da 'utf-8'. Gli spazi vengono quindi trasformati in trattini, tutto viene ridimensionato, ecc.


Sebbene funzioni perfettamente, in qualche modo ritengo che non sia molto efficiente.
WhyNotHugo,

2

È possibile utilizzare il seguente metodo di supporto. Può convertire i caratteri 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("--","-");
}

2

Ecco la mia versione (più lenta, ma divertente da scrivere) del codice di 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);
}

La mia stringa di test:

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


2

La soluzione stackoverflow è eccezionale, ma il browser moderno (escluso IE, come al solito) ora gestisce bene la codifica utf8:

inserisci qui la descrizione dell'immagine

Quindi ho aggiornato la soluzione proposta:

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));
            }
    ...
}

Codice completo su Pastebin

Modifica: ecco il codice per il RemapInternationalCharToAsciimetodo (che manca nel pastebin).


Secondo Wikipedia , Mozilla 1.4, Netscape 7.1, Opera 7.11 sono state tra le prime applicazioni a supportare IDNA. Un plug-in del browser è disponibile per Internet Explorer 6 per fornire supporto IDN. Le API URL di Internet Explorer 7.0 e Windows Vista forniscono supporto nativo per IDN. Sembra che rimuovere i caratteri UTF-8 sia una perdita di tempo. Lunga vita a UTF-8 !!!
Muhammad Rehan Saeed,

1

Mi è piaciuto il modo in cui questo è fatto senza usare espressioni regolari , quindi l'ho portato su PHP. Ho appena aggiunto una funzione chiamata is_betweenper controllare i caratteri:

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


1

Ho portato il codice su TypeScript. Può essere facilmente adattato a JavaScript.

Sto aggiungendo un .containsmetodo al Stringprototipo, se stai prendendo di mira gli ultimi browser o ES6 puoi .includesinvece usare .

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

0

No, no, no. Ti sbagli di grosso. Fatta eccezione per la roba dei segni diacritici, ci stai arrivando, ma per quanto riguarda i personaggi asiatici (peccato per gli sviluppatori di Ruby per non aver considerato i loro fratelli Nihonjin ).

Firefox e Safari mostrano entrambi caratteri non ASCII nell'URL e, francamente, hanno un bell'aspetto. È bello supportare link come " http://somewhere.com/news/read/ お 前 た ち は ア ホ じ ゃ な い か い ".

Quindi ecco un po 'di codice PHP che lo farà, ma l'ho appena scritto e non l'ho provato a stress.

<?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;
    }

Esempio:

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

Uscite: コ リ ン -e-- ト ー マ ス -e- ア ー ノ ル ド

'-And-' è perché & 's viene cambiato in' -and- '.


4
Davvero non so cosa dire su questa informazione.
sabato

3
Questo è un ottimo esempio di quando NON usare un'istruzione case switch.
NickG
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.