Como o Stack Overflow gera seus URLs compatíveis com SEO?

253

O que é uma boa expressão regular completa ou algum outro processo que levaria o título:

Como você altera um título para fazer parte do URL, como Stack Overflow?

e transformá-lo em

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

usado nos URLs compatíveis com SEO no Stack Overflow?

O ambiente de desenvolvimento que estou usando é o Ruby on Rails , mas se houver outras soluções específicas da plataforma (.NET, PHP, Django ), eu adoraria vê-las também.

Tenho certeza de que eu (ou outro leitor) encontrará o mesmo problema em uma plataforma diferente.

Estou usando rotas personalizadas e quero saber principalmente como alterar a string para que todos os caracteres especiais sejam removidos, tudo em minúsculas e todo o espaço em branco seja substituído.

limpador
fonte
E quanto a personagens engraçados? O que você vai fazer sobre isso? Umlauts? Pontuação? Estes precisam ser considerados. Basicamente, eu usaria uma abordagem de lista branca, em oposição às abordagens de lista negra acima: descreva quais caracteres você permitirá, quais caracteres serão convertidos (para quê?) E depois alterem o restante para algo com significado ("") . Duvido que você possa fazer isso em uma regex ... Por que não apenas repetir os caracteres?
Daren Thomas
1
Deve ser migrado para meta ; como a pergunta e a resposta lidam especificamente com a implementação do SO, e a resposta aceita é de @JeffAtwood.
casperOne
19
@casperOne Você acha que Jeff não tem permissão para ter uma reputação não meta? A questão é sobre "como alguém pode fazer algo assim", não especificamente "como isso é feito aqui".
Paŭlo Ebermann
@ PaŭloEbermann: Não se trata de Jeff ter uma reputação não meta (a quantidade de reputação que ele tem realmente não é da minha conta); o corpo da pergunta referenciou especificamente a implementação do StackOverflow, portanto, a justificativa para ele estar na meta.
casperOne

Respostas:

300

Aqui está como fazemos. Observe que provavelmente existem mais condições de borda do que você imagina à primeira vista.

Esta é a segunda versão, desenrolada para 5x mais desempenho (e sim, eu a comparei). Achei que seria otimizado porque essa função pode ser chamada centenas de vezes por página.

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

Para ver a versão anterior do código substituída (mas é funcionalmente equivalente e 5x mais rápida), veja o histórico de revisões desta postagem (clique no link da data).

Além disso, o RemapInternationalCharToAsciicódigo fonte do método pode ser encontrado aqui .

Jeff Atwood
fonte
24
Seria bom com uma versão que não apenas uma gota caracteres acentuados como AAO mas em vez deaccentuate-los para aao ... ^^
Oskar Duveborn
22
@oskar um toco de que RemapInternationalCharToAscii()a função está lá meta.stackexchange.com/questions/7435/...
Jeff Atwood
3
Isso é ótimo. A única mudança que eu fiz até agora é mudar "if (i == maxlen) break;" tornar-se "if (sb.Length == maxlen) break;" apenas no caso existem muitos caracteres inválidos na cadeia eu estou passando no.
Tom Chantler
4
Uma otimização menor: em if (prevdash) sb.Length -= 1; return sb.ToString();vez da última ifdeclaração.
Mark Hurd
8
O @Dommer sb.Length == maxlen break;está com erros se o sinal no maxLenght-1 for "ß", ele for convertido para "ss" sb.Length == maxlenenunca será verdadeiro, é melhor testar (sb.Length > = maxlen).
Henrik Stenbæk 29/03
32

Aqui está minha versão do código de Jeff. Fiz as seguintes alterações:

  • Os hífens foram anexados de forma que um pudesse ser adicionado e, em seguida, era necessário removê-lo, pois era o último caractere da string. Ou seja, nunca queremos "minha lesma". Isso significa uma alocação de sequência extra para removê-la neste caso de aresta. Eu resolvi isso atrasando o hífen. Se você comparar meu código com o de Jeff, é fácil seguir a lógica para isso.
  • Sua abordagem é puramente baseada em pesquisa e perdeu muitos caracteres que encontrei em exemplos enquanto pesquisava no Stack Overflow. Para combater isso, primeiro procuro um passo de normalização (agrupamento AKA mencionado na pergunta Meta Stack Overflow). Caracteres não USCI ASCII descartados do URL completo (perfil) ) e depois ignoro quaisquer caracteres fora dos intervalos aceitáveis. Isso funciona na maioria das vezes ...
  • ... Para quando não, eu também tive que adicionar uma tabela de pesquisa. Como mencionado acima, alguns caracteres não são mapeados para um valor ASCII baixo quando normalizados. Em vez de descartá-las, tenho uma lista manual de exceções que sem dúvida está cheia de buracos, mas é melhor que nada. O código de normalização foi inspirado no ótimo post de Jon Hanna na pergunta Stack Overflow. Como remover acentos em uma string? .
  • A conversão de caso agora também é opcional.

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

Para obter mais detalhes, os testes de unidade e uma explicação de por que o esquema de URL do Facebook é um pouco mais inteligente que o Stack Overflows, tenho uma versão expandida disso no meu blog .

DanH
fonte
4
+1 Isso é ótimo, Dan. Também adicionei um comentário no seu blog sobre a possibilidade de mudar if (i == maxlen) break;para que, em if (sb.Length == maxlen) break;vez disso, se você passar uma string com muitos caracteres em branco / inválidos, ainda poderá obter uma lesma do comprimento desejado, enquanto o código em si pode acabar truncando massivamente (por exemplo, considere o caso em que você começa com 80 espaços ...). E um benchmark aproximado de 10.000.000 de iterações contra o código de Jeff mostrou que ele tem aproximadamente a mesma velocidade.
Tom Chantler
1
Obrigado, respondeu no meu blog e corrigiu o código lá e acima. Também obrigado por comparar o código. Para os interessados, era parecido com o de Jeff.
1111 DanH
2
Parece que há alguns problemas com Slug.Create (): as versões em maiúsculas de ÆØÅ não são convertidas corretamente ÆØ é ignorado enquanto Å é traduzido para a. Normalmente você converterá "å" para "aa", "ø" para "oe" e "æ" para "ae". Segunda interrupção (sb.Length == maxlen); é incorreto se o sinal no maxLenght-1 for "ß" (sb.Length == maxlen) nunca será verdadeiro, é melhor testar (sb.Length> = maxlen). Estou convencido de que você cortou em qualquer posição aleatória e não cortou no último "-", isso o impedirá de terminar com uma palavra não desejada no final: como se você tivesse que cortar "para afirmar" após o último "s "
Henrik Stenbæk
@ DanH seria ótimo ter a versão javascript do código.
Freshblood
16

Você desejará configurar uma rota personalizada para apontar a URL para o controlador que a manipulará. Como você está usando Ruby on Rails, aqui está uma introdução ao uso do mecanismo de roteamento.

No Ruby, você precisará de uma expressão regular como você já conhece e aqui está a expressão regular a ser usada:

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

Você também pode usar esta função JavaScript para geração em forma de lesmas (esta é baseada / copiada do 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
fonte
Adicionando alguns vamos ou const seria ótimo, pois isso não é JS de baunilha.
Aditya Anand
8

Para uma boa medida, aqui está a função PHP no WordPress que faz isso ... Eu acho que o WordPress é uma das plataformas mais populares que usam links sofisticados.

    função sanitize_title_with_dashes ($ title) {
            $ title = strip_tags ($ title);
            // Preservar octetos com escape.
            $ title = preg_replace ('|% ([a-fA-F0-9] [a-fA-F0-9]) |', '--- $ 1 ---', $ title);
            // Remova os sinais de porcentagem que não fazem parte de um octeto.
            $ title = str_replace ('%', '', $ título);
            // Restaurar octetos.
            $ title = preg_replace ('| --- ([a-fA-F0-9] [a-fA-F0-9]) --- |', '% $ 1', $ title);
            $ title = remove_accents ($ title);
            if (appear_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 ('/&.+?;/', '', $ título); // mata entidades
            $ title = preg_replace ('/ [^% a-z0-9 _-] /', '', $ title);
            $ title = preg_replace ('/ \ s + /', '-', $ título);
            $ title = preg_replace ('| - + |', '-', $ título);
            $ title = trim ($ title, '-');
            retornar $ title;
    }

Esta função, bem como algumas das funções de suporte, podem ser encontradas em wp-includes / formatting.php.

O Geek Como Fazer
fonte
6
Esta não é a resposta completa. Está faltando funções como: remove_accents, seems_utf8...
Nikola Loncar
a completa @The How-To Geek responder ainda é possível git clone git://core.git.wordpress.org/e encontrar o wp-includes/formatting.phparquivo em
mickro
5

Se você estiver usando a borda do Rails, poderá confiar no Inflector.parametrize - aqui está o exemplo da documentação:

  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>

Além disso, se você precisar lidar com caracteres mais exóticos, como acentos (éphémère) na versão anterior do Rails, poderá usar uma mistura de PermalinkFu e DiacriticsFu :

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

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

Não estou familiarizado com o Ruby on Rails, mas o seguinte é o código PHP (não testado). Você provavelmente pode traduzir isso muito rapidamente para Ruby on Rails, se achar útil.

$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

Eu espero que isso ajude.

Vegard Larsen
fonte
4

Eu não gosto muito de Ruby ou Rails, mas em Perl, é isso que eu faria:

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

Acabei de fazer um teste rápido e parece funcionar. Espero que isso seja relativamente fácil de traduzir para Ruby.

Brian
fonte
4

Implementação T-SQL, adaptada 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
fonte
4

Eu sei que é uma pergunta muito antiga, mas como a maioria dos navegadores agora suporta URLs unicode , encontrei uma ótima solução no XRegex que converte tudo, exceto letras (em todos os idiomas para '-').

Isso pode ser feito em várias linguagens de programação.

O padrão é \\p{^L}+e você só precisa usá-lo para substituir todas as letras que não sejam '-'.

Exemplo de trabalho no node.js com o módulo 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
fonte
3

Supondo que sua classe de modelo tenha um atributo title, você pode simplesmente substituir o método to_param dentro do modelo, assim:

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

Este episódio do Railscast tem todos os detalhes. Você também pode garantir que o título contenha apenas caracteres válidos usando este:

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

Código de Brian, em Ruby:

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

downcasetransforma a string para minúsculas, stripremove esquerda e à direita espaço em branco, a primeira gsubchamada g lobally sub espaços stitutes com traços, eo segundo remove tudo o que não é uma letra ou um traço.

Sören Kuklau
fonte
2

Existe um pequeno plugin Ruby on Rails chamado PermalinkFu , que faz isso. O método de escape faz a transformação em uma sequência que é adequada para uma URL . Dê uma olhada no código; esse método é bastante simples.

Para remover caracteres não ASCII , ele usa a iconv lib para traduzir para 'ascii // ignore // translit' de 'utf-8'. Os espaços são então transformados em traços, tudo é minucioso etc.

Lau
fonte
Enquanto isso funciona perfeitamente, de alguma forma, sinto que não é muito eficiente.
WhyNotHugo
2

Você pode usar o seguinte método auxiliar. Ele pode converter os caracteres 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
fonte
2

Aqui está a minha versão (mais lenta, mas divertida de escrever) do código 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);
}

Minha sequência de teste:

" 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
fonte
2

A solução stackoverflow é ótima, mas o navegador moderno (excluindo o IE, como de costume) agora lida com a codificação utf8:

insira a descrição da imagem aqui

Então, atualizei a solução 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));
            }
    ...
}

Código completo em Pastebin

Edit: Aqui está o código do RemapInternationalCharToAsciimétodo (que está faltando no pastebin).

giammin
fonte
Segundo a Wikipedia , o Mozilla 1.4, Netscape 7.1, Opera 7.11 estavam entre os primeiros aplicativos a oferecer suporte ao IDNA. Um plug-in de navegador está disponível para o Internet Explorer 6 para fornecer suporte a IDN. O Internet Explorer 7.0 e as APIs de URL do Windows Vista fornecem suporte nativo para IDN. Parece que remover caracteres UTF-8 é uma perda de tempo. Viva UTF-8 !!!
Muhammad Rehan Saeed
1

Eu gostei da maneira como isso é feito sem o uso de expressões regulares , então eu o transportei para o PHP. Acabei de adicionar uma função chamada is_betweenpara verificar caracteres:

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
fonte
1

Agora, todos os navegadores lidam com a codificação utf8, para que você possa usar o método WebUtility.UrlEncode , como o HttpUtility.UrlEncode usado pelo @giamin, mas que funciona fora de um aplicativo da web.

ikourfaln
fonte
1

Portei o código para o TypeScript. Pode ser facilmente adaptado ao JavaScript.

Estou adicionando um .containsmétodo ao Stringprotótipo. Se você estiver direcionando os navegadores mais recentes ou o ES6, poderá usar .includes.

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
fonte
0

Não não não. Vocês estão todos muito errados. Exceto pelo material diacrítico-fu, você está chegando lá, mas e quanto aos personagens asiáticos (que vergonha para os desenvolvedores Ruby por não considerarem seus irmãos nihonjin ).

O Firefox e o Safari exibem caracteres não ASCII no URL e, francamente, ficam ótimos. É bom oferecer suporte a links como ' http://somewhere.com/news/read/ ' '' '' '.

Então, aqui está um código PHP que fará isso, mas eu apenas escrevi e não o testei sob estresse.

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

Exemplo:

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

Saídas: コ リ and e-ー マ and e- ア ー ノ ル ド

O '- e -' é porque & é alterado para '- e -'.

Peter Mortensen
fonte
4
Realmente não sei o que dizer sobre essa informação.
SJul
3
Esse é realmente um bom exemplo de quando NÃO usar uma instrução switch case.
NickG