W jaki sposób Stack Overflow generuje przyjazne dla SEO adresy URL?

253

Co to jest dobre kompletne wyrażenie regularne lub inny proces, który mógłby przyjąć tytuł:

Jak zmienić tytuł na część adresu URL, np. Przepełnienie stosu?

i zamień to w

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

który jest używany w przyjaznych SEO adresach URL w przepełnieniu stosu?

Środowiskiem programistycznym, z którego korzystam, jest Ruby on Rails , ale jeśli istnieją inne rozwiązania specyficzne dla platformy (.NET, PHP, Django ), też chciałbym je zobaczyć.

Jestem pewien, że ja (lub inny czytelnik) napotkam ten sam problem na innej platformie.

Korzystam z niestandardowych tras i chcę przede wszystkim wiedzieć, jak zmienić ciąg znaków, aby wszystkie znaki specjalne zostały usunięte, wszystkie małe litery i wszystkie białe znaki są zastępowane.

wusher
źródło
Co z zabawnymi postaciami? Co zamierzasz z nimi zrobić? Umlauts? Interpunkcja? Należy to wziąć pod uwagę. Zasadniczo użyłbym podejścia z białej listy, w przeciwieństwie do powyższych podejść do czarnej listy: Opisz, które znaki pozwolisz, które znaki będziesz konwertować (na co?), A następnie zmień resztę na coś znaczącego („”) . Wątpię, czy możesz to zrobić za pomocą jednego wyrażenia regularnego ... Dlaczego nie po prostu przechodzić między postaciami?
Daren Thomas
1
Należy przenieść do meta ; ponieważ zarówno pytanie, jak i odpowiedź dotyczą konkretnie implementacji SO, a zaakceptowana odpowiedź pochodzi od @JeffAtwood.
casperOne
19
@casperOne Czy uważasz, że Jeff nie ma reputacji innej niż meta? Pytanie dotyczy „jak to zrobić”, a nie „jak to się tutaj robi”.
Paŭlo Ebermann
@ PaŭloEbermann: Nie chodzi o to, żeby Jeff uzyskał trochę nie-meta reputacji (ile reputacji on naprawdę nie jest moim zmartwieniem); treść pytania konkretnie odnosiła się do implementacji StackOverflow, stąd uzasadnienie tego, że jest na meta.
casperOne

Odpowiedzi:

300

Oto jak to robimy. Zauważ, że prawdopodobnie jest więcej warunków brzegowych, niż ci się wydaje na pierwszy rzut oka.

Jest to druga wersja, rozwinięta w celu uzyskania 5-krotnie większej wydajności (i tak, przetestowałem ją). Pomyślałem, że zoptymalizuję tę funkcję, ponieważ tę funkcję można wywołać setki razy na stronę.

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

Aby zobaczyć poprzednią wersję kodu, który został zastąpiony (ale jest funkcjonalnie równoważny i 5 razy szybszy), przejrzyj historię zmian tego postu (kliknij link daty).

Również RemapInternationalCharToAsciikod źródłowy metody można znaleźć tutaj .

Jeff Atwood
źródło
24
Byłoby miło z wersją, która nie tylko upuszcza znaki akcentowane, takie jak åäö, ale zamiast tego deaccentuuje je do aao ... ^^
Oskar Duveborn
22
@oskar skrót tej RemapInternationalCharToAscii()funkcji jest tam meta.stackexchange.com/questions/7435/…
Jeff Atwood
3
To jest świetne. Jedyną zmianą, jaką do tej pory dokonałem, jest zmiana „jeśli (i == makslen) przerwa;” stać się „if (sb.Length == maxlen) break;” na wszelki wypadek w ciągu, który przekazuję, jest wiele nieprawidłowych znaków.
Tom Chantler
4
Drobna optymalizacja: if (prevdash) sb.Length -= 1; return sb.ToString();zamiast ostatniej ifinstrukcji.
Mark Hurd
8
@Dommer sb.Length == maxlen break;jest błędny, jeśli znak na maxLenght-1 to „ß”, zostanie przekonwertowany na „ss” sb.Length == maxlene, nigdy nie będzie prawdą, lepiej zamiast tego sprawdzić (sb.Length > = maxlen).
Henrik Stenbæk
32

Oto moja wersja kodu Jeffa. Wprowadziłem następujące zmiany:

  • Łączniki zostały dodane w taki sposób, że można je dodać, a następnie należy usunąć, ponieważ był to ostatni znak w ciągu. Oznacza to, że nigdy nie chcemy „mojego ślimaka”. Oznacza to dodatkowy przydział ciągu, aby usunąć go w tym przypadku krawędzi. Obejrzałem to przez dzielenie opóźnień. Jeśli porównasz mój kod z Jeffem, logika tego jest łatwa do naśladowania.
  • Jego podejście opiera się wyłącznie na wyszukiwaniu i pominęło wiele postaci, które znalazłem w przykładach podczas badań nad przepełnieniem stosu. Aby temu przeciwdziałać, najpierw wykonuję przepustkę normalizacyjną (zestawienie AKA wspomniane w pytaniu o przepełnienie stosu meta stosu Znaki spoza US-ASCII zostały usunięte z pełnego adresu URL (profilu) ), a następnie ignoruję wszelkie znaki spoza dopuszczalnych zakresów. Działa to przez większość czasu ...
  • ... Bo gdy tak nie jest, musiałem dodać tabelę odnośników. Jak wspomniano powyżej, niektóre znaki nie są mapowane na niską wartość ASCII podczas normalizacji. Zamiast ich upuścić, mam ręczną listę wyjątków, która jest niewątpliwie pełna dziur, ale jest lepsza niż nic. Kod normalizacyjny został zainspirowany świetnym postem Jona Hanny w pytaniu Przepełnienie stosu Jak mogę usunąć akcenty z łańcucha? .
  • Konwersja spraw jest teraz również opcjonalna.

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

Aby uzyskać więcej informacji, testy jednostkowe i wyjaśnienie dlaczego Facebook „s URL schemat jest trochę mądrzejszy niż Stos przepełnienie, Mam rozszerzoną wersję to na moim blogu .

DanH
źródło
4
+1 To jest świetny Dan. Dodałem również komentarz na twoim blogu o możliwej zmianie if (i == maxlen) break;na if (sb.Length == maxlen) break;zamiast, aby po przekazaniu ciągu z dużą ilością białych / niepoprawnych znaków nadal można było uzyskać żądaną długość, podczas gdy kod w obecnej postaci może skończyć masowo go obcinając (np. rozważ przypadek, w którym zaczynasz z 80 spacjami ...). A przybliżony test 10 000 000 iteracji w stosunku do kodu Jeffa pokazał, że jest to mniej więcej ta sama prędkość.
Tom Chantler,
1
Dzięki, odpowiedział na moim blogu i naprawił kod tam i powyżej. Dziękujemy również za testowanie kodu. Dla zainteresowanych było na równi z Jeffem.
DanH
2
Wygląda na to, że występują pewne problemy ze Slug.Create (): Wielkie wersje ÆØÅ nie są poprawnie konwertowane ÆØ jest ignorowane, gdy Å jest tłumaczone na a. Zwykle konwertujesz „å” na „aa”, „ø” na „oe” i „æ” na „ae”. Druga przerwa (sb.Length == maxlen); jest błędny, jeśli znak na maxLenght-1 to „ß” (sb.Length == maxlen) nigdy nie będzie prawdziwy, lepiej zamiast tego sprawdzić (sb.Length> = maxlen). Mam wrażenie, że przecinasz dowolną przypadkową pozycję i nie przerywasz ostatniego „-”, to uratuje cię przed zakończeniem niepotrzebnym słowem na końcu: tak jakbyś musiał wyciąć „aby potwierdzić” po ostatnich „s” „
Henrik Stenbæk
@ DanH byłoby wspaniale mieć javascriptową wersję kodu.
Freshblood,
16

Będziesz chciał ustawić niestandardową trasę, aby adres URL wskazywał kontroler, który ją obsłuży. Ponieważ używasz Ruby on Rails, oto wprowadzenie do korzystania z ich silnika routingu.

W Ruby potrzebujesz wyrażenia regularnego, takiego jak już znasz, a oto wyrażenie regularne do użycia:

def permalink_for(str)
    str.gsub(/[^\w\/]|[!\(\)\.]+/, ' ').strip.downcase.gsub(/\ +/, '-')
end
Dale Ragan
źródło
11

Możesz także użyć tej funkcji JavaScript do generowania ślimaka w formie (ta jest oparta na / skopiowana z 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
źródło
Dodanie niektórych let lub const byłoby świetne, ponieważ nie jest to waniliowy JS.
Aditya Anand
8

Na wszelki wypadek, oto funkcja PHP w WordPress, która to robi ... Myślę, że WordPress jest jedną z bardziej popularnych platform, która wykorzystuje fantazyjne linki.

    funkcja sanitize_title_with_dashes ($ title) {
            $ title = strip_tags ($ title);
            // Zachowaj ocalone oktety.
            $ title = preg_replace ('|% ([a-fA-F0-9] [a-fA-F0-9]) |', '--- $ 1 ---', $ tytuł);
            // Usuń znaki procentu, które nie są częścią oktetu.
            $ title = str_replace ('%', '', $ title);
            // Przywróć oktety.
            $ title = preg_replace ('| --- ([a-fA-F0-9] [a-fA-F0-9]) --- |', '% $ 1', $ title);
            $ title = remove_accents ($ title);
            if (wydaje_utf8 ($ tytuł)) {
                    if (function_exists ('mb_strtolower')) {
                            $ title = mb_strtolower ($ title, 'UTF-8');
                    }
                    $ title = utf8_uri_encode ($ title, 200);
            }
            $ title = strtolower ($ title);
            $ title = preg_replace ('/&.+?;/', '', $ title); // zabijaj byty
            $ title = preg_replace ('/ [^% a-z0-9 _-] /', '', $ title);
            $ title = preg_replace ('/ \ s + /', '-', $ title);
            $ title = preg_replace ('| - + |', '-', $ title);
            $ title = trim ($ title, '-');
            zwróć $ tytuł;
    }

Ta funkcja, a także niektóre funkcje pomocnicze można znaleźć w wp-include / formatting.php.

Maniak instruktażowy
źródło
6
To nie jest pełna odpowiedź. Brakuje funkcji, takich jak: remove_accents, seems_utf8...
Nikola Loncar
uzupełnić @ How-To Geek odpowiedź, którą wciąż możesz git clone git://core.git.wordpress.org/znaleźć i znaleźć wp-includes/formatting.phpplik
mickro
5

Jeśli używasz krawędzi Railsów, możesz polegać na Inflector.parametrize - oto przykład z dokumentacji:

  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>

Również jeśli potrzebujesz obsługiwać bardziej egzotyczne znaki, takie jak akcenty (éphémère) w poprzedniej wersji Railsów, możesz użyć kombinacji PermalinkFu i DiacriticsFu :

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

DiacriticsFu::escape("räksmörgås")
=> "raksmorgas"
Thibaut Barrère
źródło
5

Nie znam Ruby on Rails, ale poniżej znajduje się (nieprzetestowany) kod PHP. Prawdopodobnie możesz to bardzo szybko przetłumaczyć na Ruby on Rails, jeśli uznasz to za przydatne.

$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

Mam nadzieję, że to pomoże.

Vegard Larsen
źródło
4

Nie mam wiele na temat Ruby ani Railsów, ale w Perlu tak postąpiłbym:

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

Właśnie zrobiłem szybki test i wydaje się, że działa. Mam nadzieję, że jest to stosunkowo łatwe do przetłumaczenia na Ruby.

Brian
źródło
4

Implementacja T-SQL, zaadaptowana z 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
źródło
4

Wiem, że to bardzo stare pytanie, ale ponieważ większość przeglądarek obsługuje teraz adresy Unicode , znalazłem świetne rozwiązanie w XRegex, które konwertuje wszystko oprócz liter (we wszystkich językach na „-”).

Można to zrobić w kilku językach programowania.

Wzór jest, \\p{^L}+a następnie wystarczy go użyć, aby zastąpić wszystkie nieliterowe litery na „-”.

Przykład działania w node.js z modułem 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
źródło
3

Zakładając, że twoja klasa modelu ma atrybut title, możesz po prostu przesłonić metodę to_param w modelu, w następujący sposób:

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

Ten odcinek Railscast zawiera wszystkie szczegóły. Możesz również upewnić się, że tytuł zawiera tylko prawidłowe znaki, używając tego:

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

Kod Briana w języku Ruby:

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

downcaseOkazuje się ciąg małych liter, stripusuwa początkowe i końcowe białe znaki, pierwsze gsubwezwanie g lobally sub instytutów przestrzenie z kreskami, a drugi usuwa wszystko, co nie jest literą lub myślnik.

Sören Kuklau
źródło
2

Jest mała wtyczka Ruby on Rails o nazwie PermalinkFu , która to robi. Metoda ucieczki robi przekształcenia na ciąg znaków, który jest odpowiedni dla adresu URL . Spójrz na kod; ta metoda jest dość prosta.

Aby usunąć znaki spoza ASCII , używa iconv lib do tłumaczenia na „ascii // ignore // translit” z „utf-8”. Spacje są następnie przekształcane w myślniki, wszystko jest pisane małymi literami itp.

Lau
źródło
Chociaż działa to idealnie, wydaje mi się, że nie jest zbyt wydajne.
WhyNotHugo,
2

Możesz użyć następującej metody pomocnika. Może konwertować znaki 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
źródło
2

Oto moja (wolniejsza, ale przyjemniejsza do napisania) wersja kodu Jeffa:

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

Mój ciąg testowy:

" 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
źródło
2

Rozwiązanie Stackoverflow jest świetne, ale nowoczesna przeglądarka (z wyjątkiem IE, jak zwykle) teraz obsługuje ładnie kodowanie utf8:

wprowadź opis zdjęcia tutaj

Zaktualizowałem więc proponowane rozwiązanie:

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

Pełny kod na Pastebin

Edycja: Oto kod dla RemapInternationalCharToAsciimetody (to brakuje w pastebin).

giammin
źródło
Według Wikipedii , Mozilla 1.4, Netscape 7.1, Opera 7.11 były jednymi z pierwszych aplikacji obsługujących IDNA. Dostępna jest wtyczka do przeglądarki Internet Explorer 6, która zapewnia obsługę IDN. Interfejsy API URL programu Internet Explorer 7.0 i Windows Vista zapewniają natywną obsługę IDN. Wygląda na to, że usunięcie znaków UTF-8 to strata czasu. Niech żyje UTF-8 !!!
Muhammad Rehan Saeed
1

Podobał mi się sposób, w jaki odbywa się to bez użycia wyrażeń regularnych , więc przeniosłem go na PHP. Właśnie dodałem funkcję wywoływaną is_betweendo sprawdzania znaków:

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
źródło
1

Teraz wszystkie przeglądarki ładnie obsługują kodowanie utf8, dzięki czemu można używać metody WebUtility.UrlEncode , podobnie jak HttpUtility.UrlEncode używanej przez @giamin, ale działa ona poza aplikacją internetową.

ikourfaln
źródło
1

Przeniesiłem kod do TypeScript. Można go łatwo dostosować do JavaScript.

Dodaję .containsmetodę do Stringprototypu, jeśli celujesz w najnowsze przeglądarki lub ES6, możesz .includeszamiast tego użyć .

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
źródło
0

Nie nie nie. Wszyscy bardzo się mylicie. Z wyjątkiem rzeczy diacritics-fu, dostaniesz się tam, ale co z postaciami azjatyckimi (szkoda twórców Ruby za to, że nie brali pod uwagę swoich braci nihonjin ).

Zarówno Firefox, jak i Safari wyświetlają w adresie URL znaki spoza ASCII i szczerze mówiąc, wyglądają świetnie. Miło jest wspierać linki takie jak „ http://somewhere.com/news/read/ お 前 た ち は ア ホ じ ゃ な い か い ”.

Oto kod PHP, który to zrobi, ale właśnie go napisałem i nie przetestowałem go.

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

Przykład:

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

Wyjścia: コ リ ン -and- ト ー マ ス -and- ア ー ノ ル ド

„-And-” jest spowodowane tym, że & zmieniono na „-and-”.

Peter Mortensen
źródło
4
Naprawdę nie wiem, co powiedzieć o tej informacji.
sjas
3
To naprawdę dobry przykład, kiedy NIE należy używać instrukcji case switch.
NickG,