스택 오버플로는 SEO 친화적 인 URL을 어떻게 생성합니까?


253

좋은 정규 표현식 이나 제목을 취하는 다른 프로세스 는 무엇입니까?

스택 오버플로와 같은 URL의 일부로 제목을 어떻게 변경합니까?

그것을

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

스택 오버플로의 SEO 친화적 인 URL에 사용됩니까?

내가 사용하는 개발 환경은 Ruby on Rails 이지만 다른 플랫폼 별 솔루션 (.NET, PHP, Django ) 이 있다면 그 역시보고 싶습니다.

나는 (또는 다른 독자) 다른 플랫폼에서 같은 문제를 겪을 것이라고 확신합니다.

사용자 지정 경로를 사용하고 있으며 주로 모든 특수 문자가 제거되고 모두 소문자이며 모든 공백이 바뀌도록 문자열을 변경하는 방법을 알고 싶습니다.


재미있는 캐릭터는 어떻습니까? 당신은 그들에 대해 무엇을 할 것입니까? 움라우트? 구두? 이것들을 고려해야합니다. 기본적으로 위의 블랙리스트 접근 방식과 달리 화이트리스트 접근 방식을 사용합니다. 허용 할 문자, 변환 할 문자 (무엇으로?)를 정의한 다음 나머지를 의미있는 것으로 변경 ( "") . 하나의 정규 표현식 에서이 작업을 수행 할 수 있는지 의심 스럽습니다 ... 왜 문자를 반복하지 않습니까?
대런 토마스

1
메타 로 마이그레이션해야합니다 . 질문과 답변 모두 SO 구현을 구체적으로 다루며 허용되는 답변은 @JeffAtwood입니다.
casperOne

19
@casperOne Jeff에게 비 메타 평판이 허용되지 않는다고 생각하십니까? 문제는 "어떻게 이런 일을 할 수 있는가"가 아니라 "어떻게 이런 일을 할 수 있는가"에 관한 것이다.
Paŭlo Ebermann

@ PaŭloEbermann : Jeff가 메타가 아닌 평판을 얻는 것은 아닙니다. 질문 본문은 구체적으로 StackOverflow의 구현을 참조 하므로 메타에 대한 이론적 근거.
casperOne

답변:


300

우리가하는 방법은 다음과 같습니다. 언뜻보기에 알고있는 것보다 더 많은 엣지 조건이있을 수 있습니다.

이것은 두 번째 버전으로 5 배 더 많은 성능을 제공합니다 (예, 벤치 마크했습니다). 이 함수는 페이지 당 수백 번 호출 될 수 있기 때문에 최적화 할 것이라고 생각했습니다.

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

대체 된 코드의 이전 버전 (기능적으로 동일하지만 5 배 빠름)을 보려면이 게시물의 개정 내역을보십시오 (날짜 링크 클릭).

또한 RemapInternationalCharToAscii메소드 소스 코드는 여기 에서 찾을 수 있습니다 .


24
그냥 ... ^^ AAO 같은 악센트 부호가있는 문자를 삭제하는 대신 AAO로 deaccentuate하지 않는 버전으로 좋은 것입니다
오스카 Duveborn

22
@oskar 해당 RemapInternationalCharToAscii()기능 의 스텁은 meta.stackexchange.com/questions/7435/…
Jeff Atwood

3
대단하다. 내가 지금까지 한 유일한 변화는 "if (i == maxlen) break;" "if (sb.Length == maxlen) break"가됩니다. 내가 전달하는 문자열에 잘못된 문자가 많이있는 경우를 대비하여
Tom Chantler

4
if (prevdash) sb.Length -= 1; return sb.ToString();마지막 if진술 대신 사소한 최적화 .
Mark Hurd

8
maxLenght sb.Length == maxlen break;-1의 부호가 "ß"인 경우 @Dommer 는 버그가 있습니다. "ss"로 변환 sb.Length == maxlene되면 절대 적용되지 않습니다 (sb.Length > = maxlen). 대신 테스트하는 것이 좋습니다 .
Henrik Stenbæk

32

다음은 제 코드의 제 버전입니다. 다음과 같이 변경했습니다.

  • 하이픈은 추가 할 수있는 방식으로 추가 된 다음 문자열의 마지막 문자이므로 제거해야합니다. 즉,“my-slug-”를 원하지 않습니다. 이것은이 경우에이를 제거하기위한 추가 문자열 할당을 의미합니다. 지연 하이픈 으로이 문제를 해결했습니다. 내 코드를 Jeff의 코드와 비교하면 논리가 따르기 쉽습니다.
  • 그의 접근 방식은 순전히 조회 기반이며 스택 오버플로를 연구하는 동안 예제에서 찾은 많은 문자를 놓쳤습니다. 이 문제를 해결하기 위해 먼저 정규화 패스 (Meta Stack Overflow 질문 비 US-ASCII 문자가 전체 (프로필) URL에서 삭제됨)에 언급 된 AKA 데이터 정렬)를 수행 한 다음 허용 가능한 범위를 벗어난 문자는 무시합니다. 이것은 대부분의 시간에 작동합니다 ...
  • ... 그렇지 않은 경우 조회 테이블을 추가해야했습니다. 위에서 언급했듯이 일부 문자는 정규화 할 때 낮은 ASCII 값으로 매핑되지 않습니다. 이것을 삭제하는 대신 구멍이 가득한 예외 목록을 수동으로 얻었지만 아무것도 아닌 것보다 낫습니다. 정규화 코드는 스택 오버플로 질문에서 Jon Hanna의 위대한 게시물에서 영감을 얻었습니다 . 문자열에서 악센트를 제거하는 방법은 무엇입니까? .
  • 사례 변환도 이제 선택 사항입니다.

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

자세한 내용, 단위 테스트 및 FacebookURL 체계가 스택 오버플로보다 조금 더 똑똑한 이유에 대한 설명을 보려면 블로그에서이 버전을 확장했습니다 .


4
+1 대단하다. 나는 또한 가능하게 변경에 대한 당신의 블로그에 코멘트를 추가 if (i == maxlen) break;if (sb.Length == maxlen) break;가 끝날 수도 약자로 코드 반면, 대신 그래서 당신은 공백 / 잘못된 문자의 많은 문자열을 전달하는 경우 여전히 원하는 길이의 슬러그를 얻을 수 대량으로 잘립니다 (예 : 80 공백으로 시작하는 경우를 고려하십시오 ...). Jeff의 코드에 대한 대략 10,000,000 회 반복 벤치 마크는 대략 같은 속도 인 것으로 나타났습니다.
톰 챈 틀러

1
고마워, 내 블로그에 응답하고 그가 이상 코드를 수정했습니다. 또한 코드 벤치마킹에 감사드립니다. 관심있는 사람들에게는 Jeff 's와 동등한 수준이었습니다.
DanH

2
Slug.Create ()에 문제가있는 것 같습니다. ØØ의 대문자 버전이 제대로 변환되지 않습니다. Å가 a로 변환되는 동안 ÆØ는 무시됩니다. 일반적으로 "å"를 "aa"로, "ø"를 "oe"로, "æ"를 "ae"로 변환합니다. 두 번째 (sb. 길이 == maxlen) 휴식; maxLenght-1의 부호가 "ß"(sb.Length == maxlen) 인 경우 버그가 발생하지 않습니다 (sb.Length> = maxlen). 나는 당신이 임의의 임의의 위치를 ​​잘라 내고 마지막 "-"를 잘라 내지 않는 것을 막았습니다. 이것은 마지막에 원하지 않는 단어로 끝나는 것을 막아줍니다. "
Henrik Stenbæk

@ Danan 코드의 자바 스크립트 버전을 갖는 것이 좋을 것입니다.
Freshblood

16

URL 을 처리 할 컨트롤러로 URL 을 가리 키도록 사용자 지정 경로를 설정하려고 합니다. Ruby on Rails를 사용하고 있으므로 라우팅 엔진 사용에 대한 소개 입니다.

Ruby에서는 이미 알고있는 정규식이 필요하며 사용할 정규식은 다음과 같습니다.

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

11

JavaScript 함수를 사용 하여 슬러그의 정보를 생성 할 수 있습니다 (이것은 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
}

바닐라 JS가 아니기 때문에 let 또는 const를 추가하는 것이 좋습니다.
Aditya Anand

8

좋은 측정을 위해 여기에 WordPress의 PHP 기능이 있습니다 ... WordPress는 멋진 링크를 사용하는 가장 인기있는 플랫폼 중 하나라고 생각합니다.

    sanitize_title_with_dashes ($ title) {
            $ title = strip_tags ($ 제목);
            // 이스케이프 된 옥텟을 유지합니다.
            $ title = preg_replace ( '| % ([a-fA-F0-9] [a-fA-F0-9]) |', '--- $ 1 ---', $ title);
            // 8 진수의 일부가 아닌 퍼센트 부호를 제거합니다.
            $ title = str_replace ( '%', '', $ title);
            // 옥텟을 복원합니다.
            $ title = preg_replace ( '| --- ([a-fA-F0-9] [a-fA-F0-9]) --- |', '% $ 1', $ title);
            $ title = remove_accents ($ title);
            if (seems_utf8 ($ title)) {
                    if (function_exists ( 'mb_strtolower')) {
                            $ title = mb_strtolower ($ 제목, 'UTF-8');
                    }
                    $ title = utf8_uri_encode ($ title, 200);
            }
            $ title = strtolower ($ 제목);
            $ title = preg_replace ( '/&.+?;/', '', $ title); // 엔티티 죽이기
            $ title = preg_replace ( '/ [^ % a-z0-9 _-] /', '', $ title);
            $ title = preg_replace ( '/ \ s + /', '-', $ title);
            $ title = preg_replace ( '|-+ |', '-', $ title);
            $ title = trim ($ title, '-');
            $ title를 반환;
    }

이 기능과 일부 지원 기능은 wp-includes / formatting.php에서 찾을 수 있습니다.


6
이것은 완전한 답변이 아닙니다. 다음과 같은 기능이 없습니다 : remove_accents, seems_utf8...
Nikola Loncar

@ How-To Geek 답변을 완성하려면 여전히 파일을 git clone git://core.git.wordpress.org/찾을 수 있습니다.wp-includes/formatting.php
mickro

5

Rails Edge를 사용하는 경우 Inflector.parametrize 를 사용할 수 있습니다 . 다음은 설명서의 예입니다.

  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>

당신은 레일의 이전 버전에서 악센트 (éphémère)로 이국적인 문자를 처리 할 필요 또한, 당신은 혼합 사용할 수 있습니다 PermalinkFuDiacriticsFu를 :

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

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

5

Ruby on Rails에 익숙하지 않지만 다음은 PHP 코드입니다. 유용한 경우 Ruby on Rails로 매우 빠르게 번역 할 수 있습니다.

$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

이게 도움이 되길 바란다.


4

Ruby 나 Rails는별로 없지만 Perl에서는 다음과 같이합니다.

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

방금 빠른 테스트를했는데 효과가있는 것 같습니다. 바라건대 이것은 루비로 번역하기가 비교적 쉽습니다.


4

dbo.UrlEncode 에서 수정 된 T-SQL 구현 :

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

나는 그것이 매우 오래된 질문이라는 것을 알고 있지만 대부분의 브라우저는 이제 유니 코드 URL을 지원하기 때문에 XRegex 에서 문자를 제외한 모든 것을 (모든 언어에서 '-'로) 변환 하는 훌륭한 솔루션을 찾았습니다 .

여러 프로그래밍 언어로 수행 할 수 있습니다.

패턴은 \\p{^L}+이제 문자가 아닌 모든 문자를 '-'로 바꾸는 데 사용해야합니다.

xregex 모듈이 있는 node.js의 작업 예제

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

모델 클래스에 title 속성이 있다고 가정하면 다음과 같이 모델 내에서 to_param 메소드를 대체 할 수 있습니다.

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

이 Railscast 에피소드 에는 모든 세부 사항이 있습니다. 다음을 사용하여 제목에 유효한 문자 만 포함되도록 할 수도 있습니다.

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

2

루비에서 브라이언의 코드 :

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

downcase, 소문자로 문자열을 전환 strip선행 및 후행 공백, 첫 번째 제거 gsubg lobally 하위 대시 stitutes 공간을하고, 문자 나 대시없는 두 번째 제거합니다 모든 것을.


2

PermalinkFu 라는 작은 Ruby on Rails 플러그인 이 있습니다. 탈출 방법은 A에 대한 적합한 문자열로 변환을 수행 URL . 코드를 살펴보십시오. 그 방법은 매우 간단합니다.

비를 제거하려면ASCII가 문자 iconv lib를 사용하여 'utf-8'에서 'ascii // ignore // translit'으로 변환하십시오. 그런 다음 공백이 대시로 바뀌고 모든 것이 다운됩니다.


이것이 완벽하게 작동하는 동안 나는 어떻게 든 그것이 효율적이지 않다고 생각합니다.
WhyNotHugo

2

다음과 같은 헬퍼 방법을 사용할 수 있습니다. 유니 코드 문자를 변환 할 수 있습니다.

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

다음은 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);
}

내 테스트 문자열 :

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


2

유래 솔루션은 매우 중요하지만, 현대적인 브라우저 (평소와 같이, IE 제외) 지금 잘 UTF8 인코딩을 처리 :

여기에 이미지 설명을 입력하십시오

그래서 제안 된 솔루션을 업그레이드했습니다.

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

Pastebin의 전체 코드

편집 : 여기RemapInternationalCharToAscii메소드 코드 가 있습니다 (pastebin에는 없습니다).


Wikipedia 에 따르면 Mozilla 1.4, Netscape 7.1, Opera 7.11은 IDNA를 지원하는 최초의 애플리케이션 중 하나였습니다. Internet Explorer 6에서 IDN 지원을 제공하는 브라우저 플러그인이 제공됩니다. Internet Explorer 7.0 및 Windows Vista의 URL API는 IDN을 기본적으로 지원합니다. UTF-8 문자를 제거하는 것과 같은 소리는 시간 낭비입니다. 긴 라이브 UTF-8 !!!
무하마드 Rehan Saeed

1

정규 표현식 을 사용하지 않고이 작업을 수행하는 방식이 마음에 들었 으므로 PHP로 이식했습니다. 방금 is_between문자를 확인 하는 함수를 추가했습니다 .

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

코드를 TypeScript로 이식했습니다. JavaScript에 쉽게 적용 할 수 있습니다.

최신 브라우저 나 ES6을 대상으로하는 경우 프로토 타입에 .contains방법을 추가하고 대신 String사용할 수 있습니다 .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;
        }

0

아니, 아니. 당신은 모두 너무 잘못입니다. 분음 부호를 제외하고는 아시아의 캐릭터에 관한 것입니다 (루비 개발자에게는 니혼 진 형제를 고려하지 않은 것이 수치입니다 ).

Firefox와 Safari는 모두 비 ASCII 문자를 URL 에 표시하며 솔직히 멋지게 보입니다. ' http://somewhere.com/news/read/ お 前 た ち は ア ホ じ ゃ な い か い ' 와 같은 링크를 지원하는 것이 좋습니다 .

여기에 PHP 코드가 있지만, 방금 작성했지만 스트레스 테스트를하지 않았습니다.

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

예:

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

출력 : 코린과 토마스와 아놀드

'-and-'는 &가 '-and-'로 변경 되었기 때문입니다.


4
나는이 정보에 대해 무엇을 말할지 정말로 모른다.
sjas

3
switch case 문을 사용하지 않을 때의 좋은 예입니다.
NickG
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.