Stack Overflow如何生成其seo友好的url ?

什么是一个好的完整的正则表达式或其他一些进程,它可以取这样的标题:

如何将标题更改为URL的一部分,如堆栈溢出?

然后把它变成

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

在Stack Overflow上使用的seo友好的url ?

我使用的开发环境是Ruby on Rails,但如果有一些其他特定于平台的解决方案(。NET, PHP, Django),我也想看到这些。

我相信我(或其他读者)在不同的平台上也会遇到同样的问题。

我使用自定义路由,我主要想知道如何改变字符串的所有特殊字符被删除,它都是小写的,所有空白被替换。

44062 次浏览

你需要设置一个自定义路由,将URL指向将要处理它的控制器。因为你正在使用Ruby on Rails,这里有一个介绍在使用他们的路由引擎。

在Ruby中,你需要一个你已经知道的正则表达式,下面是要使用的正则表达式:

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

我不熟悉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

我希望这能有所帮助。

我不太了解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";

我刚做了一个快速测试,似乎有用。希望这在Ruby中比较容易翻译。

假设你的模型类有一个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'

Brian的Ruby代码:

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

downcase将字符串转换为小写,strip删除开头和结尾空格,第一个gsub调用globally __abc4用破折号替换空格,第二个调用删除所有不是字母或破折号的东西。

我们是这样做的。注意,可能有比你第一眼意识到的更多的边缘条件。

这是第二个版本,展开后的性能提高了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方法的源代码可以在在这里中找到。

为了更好地衡量,这里是WordPress中的PHP函数…我认为WordPress是使用花哨链接的最受欢迎的平台之一。

function sanitize_title_with_dashes($title) {
$title = strip_tags($title);
// Preserve escaped octets.
$title = preg_replace('|%([a-fA-F0-9][a-fA-F0-9])|', '---$1---', $title);
// Remove percent signs that are not part of an octet.
$title = str_replace('%', '', $title);
// Restore octets.
$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($title, 'UTF-8');
}
$title = utf8_uri_encode($title, 200);
}
$title = strtolower($title);
$title = preg_replace('/&.+?;/', '', $title); // kill entities
$title = preg_replace('/[^%a-z0-9 _-]/', '', $title);
$title = preg_replace('/\s+/', '-', $title);
$title = preg_replace('|-+|', '-', $title);
$title = trim($title, '-');
return $title;
}

这个函数以及一些支持函数可以在wp-includes/formatting.php中找到。

有一个名为PermalinkFu的小Ruby on Rails插件可以做到这一点。逃生的方法将转换为适合URL的字符串。看一下代码;那个方法很简单。

为了删除非-美国信息交换标准代码字符,它使用iconv库将'utf-8'转换为'ascii//ignore//translit'。然后空格变成破折号,所有东西都被缩小,等等。

你也可以使用这个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
}

T-SQL实现,改编自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

如果你正在使用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="/person/1-donald-e-knuth">Donald E. Knuth</a>

另外,如果你需要在以前版本的Rails中处理更多的外来字符,例如口音(éphémère),你可以混合使用PermalinkFuDiacriticsFu:

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


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

不,不,不。你们都错了。除了变音符符之类的东西,你差不多到了,但是亚洲字符呢(Ruby开发人员没有考虑到他们的nihonjin兄弟,真是可耻)。

Firefox和Safari都在URL中显示非ascii字符,坦率地说,它们看起来很棒。支持'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);
< p >输出: コリン- -ト,マス- -ア,ノルド

. > . cn

' and-'是因为& s会变成' and-'。

下面是Jeff代码的我的版本。我做了以下修改:

For more details, the unit tests, and an explanation of why Facebook's URL scheme is a little smarter than Stack Overflows, I've got an expanded version of this on my blog.

您可以使用以下帮助器方法。它可以转换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("--","-");
}

我喜欢这种不使用正则表达式的方式,所以我将它移植到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;
}
}

stackoverflow解决方案是伟大的,但现代浏览器(不包括IE,通常)现在很好地处理utf8编码:

enter image description here

所以我升级了建议的解决方案:

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中缺失)。

下面是我的(稍慢,但写起来很有趣)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... "

现在所有的浏览器都很好地处理utf8编码,所以你可以使用WebUtility。UrlEncode方法,它像@giamin使用的HttpUtility。UrlEncode,但它在web应用程序之外工作。

我知道这是一个非常老的问题,但由于大多数浏览器现在支持unicode 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"

我将代码移植到TypeScript中。它可以很容易地适应JavaScript。

我在String原型中添加了一个.contains方法,如果你的目标是最新的浏览器或ES6,你可以使用.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;
}

重写Jeff的代码,使其更简洁

    public static string RemapInternationalCharToAscii(char c)
{
var s = c.ToString().ToLowerInvariant();


var mappings = new Dictionary<string, string>
{
{ "a", "àåáâäãåą" },
{ "c", "çćčĉ" },
{ "d", "đ" },
{ "e", "èéêëę" },
{ "g", "ğĝ" },
{ "h", "ĥ" },
{ "i", "ìíîïı" },
{ "j", "ĵ" },
{ "l", "ł" },
{ "n", "ñń" },
{ "o", "òóôõöøőð" },
{ "r", "ř" },
{ "s", "śşšŝ" },
{ "ss", "ß" },
{ "th", "Þ" },
{ "u", "ùúûüŭů" },
{ "y", "ýÿ" },
{ "z", "żźž" }
};


foreach(var mapping in mappings)
{
if (mapping.Value.Contains(s))
return mapping.Key;
}


return string.Empty;
}