<p>What is a good complete <a href="http://en.wikipedia.org/wiki/Regular_expression" rel="noreferrer">regular expression</a> or some other process that would take the title:</p>

<blockquote>
  <p>How do you change a title to be part of the URL like Stack Overflow?</p>
</blockquote>

<p>and turn it into </p>

<pre class="lang-none prettyprint-override"><code>how-do-you-change-a-title-to-be-part-of-the-url-like-stack-overflow
</code></pre>

<p>that is used in the SEO-friendly URLs on Stack&nbsp;Overflow?</p>

<p>The development environment I am using is <a href="http://en.wikipedia.org/wiki/Ruby_on_Rails" rel="noreferrer">Ruby on Rails</a>, but if there are some other platform-specific solutions (.NET, PHP, <a href="http://en.wikipedia.org/wiki/Django_%28web_framework%29" rel="noreferrer">Django</a>), I would love to see those too. </p>

<p>I am sure I (or another reader) will come across the same problem on a different platform down the line. </p>

<p>I am using custom routes, and I mainly want to know how to alter the string to all special characters are removed, it's all lowercase, and all whitespace is replaced.</p>

[{'answer_id': 25263, 'author': 'Dale Ragan', 'author_id': 1117, 'author_profile': 'https://Stackoverflow.com/users/1117', 'pm_score': 4, 'selected': False, 'text': '<p>You will want to setup a custom route to point the <a href="http://en.wikipedia.org/wiki/Uniform_Resource_Locator" rel="nofollow noreferrer">URL</a> to the controller that will handle it. Since you are using Ruby on Rails, here is an <a href="http://manuals.rubyonrails.com/read/chapter/65" rel="nofollow noreferrer">introduction</a> in using their routing engine.</p>\n\n<p>In Ruby, you will need a regular expression like you already know and here is the regular expression to use:</p>\n\n<pre><code>def permalink_for(str)\n    str.gsub(/[^\\w\\/]|[!\\(\\)\\.]+/, \' \').strip.downcase.gsub(/\\ +/, \'-\')\nend\n</code></pre>\n'}, {'answer_id': 25275, 'author': 'Vegard Larsen', 'author_id': 1606, 'author_profile': 'https://Stackoverflow.com/users/1606', 'pm_score': 3, 'selected': False, 'text': '<p>I am not familiar with Ruby on Rails, but the following is (untested) PHP code. You can probably translate this very quickly to Ruby on Rails if you find it useful.</p>\n\n<pre><code>$sURL = "This is a title to convert to URL-format. It has 1 number in it!";\n// To lower-case\n$sURL = strtolower($sURL);\n\n// Replace all non-word characters with spaces\n$sURL = preg_replace("/\\W+/", " ", $sURL);\n\n// Remove trailing spaces (so we won\'t end with a separator)\n$sURL = trim($sURL);\n\n// Replace spaces with separators (hyphens)\n$sURL = str_replace(" ", "-", $sURL);\n\necho $sURL;\n// outputs: this-is-a-title-to-convert-to-url-format-it-has-1-number-in-it\n</code></pre>\n\n<p>I hope this helps.</p>\n'}, {'answer_id': 25279, 'author': 'Brian', 'author_id': 683, 'author_profile': 'https://Stackoverflow.com/users/683', 'pm_score': 2, 'selected': False, 'text': '<p>I don\'t much about Ruby or Rails, but in Perl, this is what I would do:</p>\n\n<pre><code>my $title = "How do you change a title to be part of the url like Stackoverflow?";\n\nmy $url = lc $title;   # Change to lower case and copy to URL.\n$url =~ s/^\\s+//g;     # Remove leading spaces.\n$url =~ s/\\s+$//g;     # Remove trailing spaces.\n$url =~ s/\\s+/\\-/g;    # Change one or more spaces to single hyphen.\n$url =~ s/[^\\w\\-]//g;  # Remove any non-word characters.\n\nprint "$title\\n$url\\n";\n</code></pre>\n\n<p>I just did a quick test and it seems to work.  Hopefully this is relatively easy to translate to Ruby.</p>\n'}, {'answer_id': 25280, 'author': 'John Topley', 'author_id': 1450, 'author_profile': 'https://Stackoverflow.com/users/1450', 'pm_score': 2, 'selected': False, 'text': '<p>Assuming that your model class has a title attribute, you can simply override the to_param method within the model, like this:</p>\n\n<pre><code>def to_param\n  title.downcase.gsub(/ /, \'-\')\nend\n</code></pre>\n\n<p><a href="http://railscasts.com/episodes/63-model-name-in-url" rel="nofollow noreferrer">This Railscast episode</a> has all the details. You can also ensure that the title only contains valid characters using this:</p>\n\n<pre><code>validates_format_of :title, :with =&gt; /^[a-z0-9-]+$/,\n                    :message =&gt; \'can only contain letters, numbers and hyphens\'\n</code></pre>\n'}, {'answer_id': 25285, 'author': 'Sören Kuklau', 'author_id': 1600, 'author_profile': 'https://Stackoverflow.com/users/1600', 'pm_score': 2, 'selected': False, 'text': "<p>Brian's code, in Ruby:</p>\n\n<pre><code>title.downcase.strip.gsub(/\\ /, '-').gsub(/[^\\w\\-]/, '')\n</code></pre>\n\n<p><code>downcase</code> turns the string to lowercase, <code>strip</code> removes leading and trailing whitespace, the first <code>gsub</code> call <em>g</em>lobally <em>sub</em>stitutes spaces with dashes, and the second removes everything that isn't a letter or a dash.</p>\n"}, {'answer_id': 25486, 'author': 'Jeff Atwood', 'author_id': 1, 'author_profile': 'https://Stackoverflow.com/users/1', 'pm_score': 9, 'selected': True, 'text': '<p>Here\'s how we do it. Note that there are probably more edge conditions than you realize at first glance.</p>\n\n<p>This is the second version, unrolled for 5x more performance (and yes, I benchmarked it). I figured I\'d optimize it because this function can be called hundreds of times per page.</p>\n\n<pre><code>/// &lt;summary&gt;\n/// Produces optional, URL-friendly version of a title, "like-this-one". \n/// hand-tuned for speed, reflects performance refactoring contributed\n/// by John Gietzen (user otac0n) \n/// &lt;/summary&gt;\npublic static string URLFriendly(string title)\n{\n    if (title == null) return "";\n\n    const int maxlen = 80;\n    int len = title.Length;\n    bool prevdash = false;\n    var sb = new StringBuilder(len);\n    char c;\n\n    for (int i = 0; i &lt; len; i++)\n    {\n        c = title[i];\n        if ((c &gt;= \'a\' &amp;&amp; c &lt;= \'z\') || (c &gt;= \'0\' &amp;&amp; c &lt;= \'9\'))\n        {\n            sb.Append(c);\n            prevdash = false;\n        }\n        else if (c &gt;= \'A\' &amp;&amp; c &lt;= \'Z\')\n        {\n            // tricky way to convert to lowercase\n            sb.Append((char)(c | 32));\n            prevdash = false;\n        }\n        else if (c == \' \' || c == \',\' || c == \'.\' || c == \'/\' || \n            c == \'\\\\\' || c == \'-\' || c == \'_\' || c == \'=\')\n        {\n            if (!prevdash &amp;&amp; sb.Length &gt; 0)\n            {\n                sb.Append(\'-\');\n                prevdash = true;\n            }\n        }\n        else if ((int)c &gt;= 128)\n        {\n            int prevlen = sb.Length;\n            sb.Append(RemapInternationalCharToAscii(c));\n            if (prevlen != sb.Length) prevdash = false;\n        }\n        if (i == maxlen) break;\n    }\n\n    if (prevdash)\n        return sb.ToString().Substring(0, sb.Length - 1);\n    else\n        return sb.ToString();\n}\n</code></pre>\n\n<p>To see the previous version of the code this replaced (but is functionally equivalent to, and 5x faster), view revision history of this post (click the date link).</p>\n\n<p>Also, the <code>RemapInternationalCharToAscii</code> method source code can be found <a href="https://meta.stackexchange.com/a/7696">here</a>.</p>\n'}, {'answer_id': 25537, 'author': 'The How-To Geek', 'author_id': 291, 'author_profile': 'https://Stackoverflow.com/users/291', 'pm_score': 3, 'selected': False, 'text': "<p>For good measure, here's the PHP function in WordPress that does it... I'd think that WordPress is one of the more popular platforms that uses fancy links.</p>\n\n<pre>\n    function sanitize_title_with_dashes($title) {\n            $title = strip_tags($title);\n            // Preserve escaped octets.\n            $title = preg_replace('|%([a-fA-F0-9][a-fA-F0-9])|', '---$1---', $title);\n            // Remove percent signs that are not part of an octet.\n            $title = str_replace('%', '', $title);\n            // Restore octets.\n            $title = preg_replace('|---([a-fA-F0-9][a-fA-F0-9])---|', '%$1', $title);\n            $title = remove_accents($title);\n            if (seems_utf8($title)) {\n                    if (function_exists('mb_strtolower')) {\n                            $title = mb_strtolower($title, 'UTF-8');\n                    }\n                    $title = utf8_uri_encode($title, 200);\n            }\n            $title = strtolower($title);\n            $title = preg_replace('/&.+?;/', '', $title); // kill entities\n            $title = preg_replace('/[^%a-z0-9 _-]/', '', $title);\n            $title = preg_replace('/\\s+/', '-', $title);\n            $title = preg_replace('|-+|', '-', $title);\n            $title = trim($title, '-');\n            return $title;\n    }\n</pre>\n\n<p>This function as well as some of the supporting functions can be found in wp-includes/formatting.php.</p>\n"}, {'answer_id': 37918, 'author': 'Lau', 'author_id': 4025, 'author_profile': 'https://Stackoverflow.com/users/4025', 'pm_score': 2, 'selected': False, 'text': '<p>There is a small Ruby on Rails plugin called <a href="http://svn.techno-weenie.net/projects/plugins/permalink_fu/" rel="nofollow noreferrer">PermalinkFu</a>, that does this. The <a href="http://svn.techno-weenie.net/projects/plugins/permalink_fu/lib/permalink_fu.rb" rel="nofollow noreferrer">escape method</a> does the transformation into a string that is suitable for a <a href="http://en.wikipedia.org/wiki/Uniform_Resource_Locator" rel="nofollow noreferrer">URL</a>. Have a look at the code; that method is quite simple.</p>\n\n<p>To remove non-<a href="http://en.wikipedia.org/wiki/ASCII" rel="nofollow noreferrer">ASCII</a> characters it uses the iconv lib to translate to \'ascii//ignore//translit\' from \'utf-8\'. Spaces are then turned into dashes, everything is downcased, etc.</p>\n'}, {'answer_id': 37922, 'author': 'fijter', 'author_id': 3215, 'author_profile': 'https://Stackoverflow.com/users/3215', 'pm_score': 4, 'selected': False, 'text': '<p>You can also use this <a href="http://en.wikipedia.org/wiki/JavaScript" rel="noreferrer">JavaScript</a> function for in-form generation of the slug\'s (this one is based on/copied from <a href="http://en.wikipedia.org/wiki/Django_%28web_framework%29" rel="noreferrer">Django</a>):</p>\n\n<pre><code>function makeSlug(urlString, filter) {\n    // Changes, e.g., "Petty theft" to "petty_theft".\n    // Remove all these words from the string before URLifying\n\n    if(filter) {\n        removelist = ["a", "an", "as", "at", "before", "but", "by", "for", "from",\n        "is", "in", "into", "like", "of", "off", "on", "onto", "per",\n        "since", "than", "the", "this", "that", "to", "up", "via", "het", "de", "een", "en",\n        "with"];\n    }\n    else {\n        removelist = [];\n    }\n    s = urlString;\n    r = new RegExp(\'\\\\b(\' + removelist.join(\'|\') + \')\\\\b\', \'gi\');\n    s = s.replace(r, \'\');\n    s = s.replace(/[^-\\w\\s]/g, \'\'); // Remove unneeded characters\n    s = s.replace(/^\\s+|\\s+$/g, \'\'); // Trim leading/trailing spaces\n    s = s.replace(/[-\\s]+/g, \'-\'); // Convert spaces to hyphens\n    s = s.toLowerCase(); // Convert to lowercase\n    return s; // Trim to first num_chars characters\n}\n</code></pre>\n'}, {'answer_id': 47633, 'author': 'Sören Kuklau', 'author_id': 1600, 'author_profile': 'https://Stackoverflow.com/users/1600', 'pm_score': 2, 'selected': False, 'text': '<p>T-SQL implementation, adapted from <a href="http://www.sqljunkies.com/WebLog/peter_debetta/archive/2007/03/09/28987.aspx" rel="nofollow noreferrer">dbo.UrlEncode</a>:</p>\n\n<pre><code>CREATE FUNCTION dbo.Slug(@string varchar(1024))\nRETURNS varchar(3072)\nAS\nBEGIN\n    DECLARE @count int, @c char(1), @i int, @slug varchar(3072)\n\n    SET @string = replace(lower(ltrim(rtrim(@string))),\' \',\'-\')\n\n    SET @count = Len(@string)\n    SET @i = 1\n    SET @slug = \'\'\n\n    WHILE (@i &lt;= @count)\n    BEGIN\n        SET @c = substring(@string, @i, 1)\n\n        IF @c LIKE \'[a-z0-9--]\'\n            SET @slug = @slug + @c\n\n        SET @i = @i +1\n    END\n\n    RETURN @slug\nEND\n</code></pre>\n'}, {'answer_id': 399903, 'author': 'Thibaut Barrère', 'author_id': 20302, 'author_profile': 'https://Stackoverflow.com/users/20302', 'pm_score': 3, 'selected': False, 'text': '<p>If you are using Rails edge, you can rely on <a href="http://github.com/rails/rails/tree/master/activesupport/lib/active_support/inflector.rb#L244" rel="nofollow noreferrer">Inflector.parametrize</a> - here\'s the example from the documentation:</p>\n\n<pre><code>  class Person\n    def to_param\n      "#{id}-#{name.parameterize}"\n    end\n  end\n\n  @person = Person.find(1)\n  # =&gt; #&lt;Person id: 1, name: "Donald E. Knuth"&gt;\n\n  &lt;%= link_to(@person.name, person_path(@person)) %&gt;\n  # =&gt; &lt;a href="/person/1-donald-e-knuth"&gt;Donald E. Knuth&lt;/a&gt;\n</code></pre>\n\n<p>Also if you need to handle more exotic characters such as accents (éphémère) in previous version of Rails, you can use a mixture of <a href="http://github.com/technoweenie/permalink_fu/tree/master" rel="nofollow noreferrer">PermalinkFu</a> and <a href="http://github.com/thbar/diacritics_fu/tree/master" rel="nofollow noreferrer">DiacriticsFu</a>:</p>\n\n<pre><code>DiacriticsFu::escape("éphémère")\n=&gt; "ephemere"\n\nDiacriticsFu::escape("räksmörgås")\n=&gt; "raksmorgas"\n</code></pre>\n'}, {'answer_id': 645130, 'author': 'Community', 'author_id': -1, 'author_profile': 'https://Stackoverflow.com/users/-1', 'pm_score': 0, 'selected': False, 'text': '<p>No, no, no. You are all so very wrong. Except for the diacritics-fu stuff, you\'re getting there, but what about Asian characters (shame on Ruby developers for not considering their <a href="http://en.wiktionary.org/wiki/nihonjin" rel="nofollow noreferrer">nihonjin</a> brethren).</p>\n\n<p>Firefox and Safari both display non-ASCII characters in the <a href="http://en.wikipedia.org/wiki/Uniform_Resource_Locator" rel="nofollow noreferrer">URL</a>, and frankly they look great. It is nice to support links like \'<a href="http://somewhere.com/news/read/%E3%81%8A%E5%89%8D%E3%81%9F%E3%81%A1%E3%81%AF%E3%82%A2%E3%83%9B%E3%81%98%E3%82%83%E3%81%AA%E3%81%84%E3%81%8B%E3%81%84" rel="nofollow noreferrer">http://somewhere.com/news/read/お前たちはアホじゃないかい</a>\'.</p>\n\n<p>So here\'s some PHP code that\'ll do it, but I just wrote it and haven\'t stress tested it.</p>\n\n<pre><code>&lt;?php\n    function slug($str)\n    {\n        $args = func_get_args();\n        array_filter($args);  //remove blanks\n        $slug = mb_strtolower(implode(\'-\', $args));\n\n        $real_slug = \'\';\n        $hyphen = \'\';\n        foreach(SU::mb_str_split($slug) as $c)\n        {\n            if (strlen($c) &gt; 1 &amp;&amp; mb_strlen($c)===1)\n            {\n                $real_slug .= $hyphen . $c;\n                $hyphen = \'\';\n            }\n            else\n            {\n                switch($c)\n                {\n                    case \'&amp;\':\n                        $hyphen = $real_slug ? \'-and-\' : \'\';\n                        break;\n                    case \'a\':\n                    case \'b\':\n                    case \'c\':\n                    case \'d\':\n                    case \'e\':\n                    case \'f\':\n                    case \'g\':\n                    case \'h\':\n                    case \'i\':\n                    case \'j\':\n                    case \'k\':\n                    case \'l\':\n                    case \'m\':\n                    case \'n\':\n                    case \'o\':\n                    case \'p\':\n                    case \'q\':\n                    case \'r\':\n                    case \'s\':\n                    case \'t\':\n                    case \'u\':\n                    case \'v\':\n                    case \'w\':\n                    case \'x\':\n                    case \'y\':\n                    case \'z\':\n\n                    case \'A\':\n                    case \'B\':\n                    case \'C\':\n                    case \'D\':\n                    case \'E\':\n                    case \'F\':\n                    case \'G\':\n                    case \'H\':\n                    case \'I\':\n                    case \'J\':\n                    case \'K\':\n                    case \'L\':\n                    case \'M\':\n                    case \'N\':\n                    case \'O\':\n                    case \'P\':\n                    case \'Q\':\n                    case \'R\':\n                    case \'S\':\n                    case \'T\':\n                    case \'U\':\n                    case \'V\':\n                    case \'W\':\n                    case \'X\':\n                    case \'Y\':\n                    case \'Z\':\n\n                    case \'0\':\n                    case \'1\':\n                    case \'2\':\n                    case \'3\':\n                    case \'4\':\n                    case \'5\':\n                    case \'6\':\n                    case \'7\':\n                    case \'8\':\n                    case \'9\':\n                        $real_slug .= $hyphen . $c;\n                        $hyphen = \'\';\n                        break;\n\n                    default:\n                       $hyphen = $hyphen ? $hyphen : ($real_slug ? \'-\' : \'\');\n                }\n            }\n        }\n        return $real_slug;\n    }\n</code></pre>\n\n<p>Example:</p>\n\n<pre><code>$str = "~!@#$%^&amp;*()_+-=[]\\{}|;\':\\",./&lt;&gt;?\\n\\r\\t\\x07\\x00\\x04 コリン ~!@#$%^&amp;*()_+-=[]\\{}|;\':\\",./&lt;&gt;?\\n\\r\\t\\x07\\x00\\x04 トーマス ~!@#$%^&amp;*()_+-=[]\\{}|;\':\\",./&lt;&gt;?\\n\\r\\t\\x07\\x00\\x04 アーノルド ~!@#$%^&amp;*()_+-=[]\\{}|;\':\\",./&lt;&gt;?\\n\\r\\t\\x07\\x00\\x04";\necho slug($str);\n</code></pre>\n\n<p>Outputs:\n    コリン-and-トーマス-and-アーノルド</p>\n\n<p>The \'-and-\' is because &amp;\'s get changed to \'-and-\'.</p>\n'}, {'answer_id': 6740497, 'author': 'DanH', 'author_id': 644682, 'author_profile': 'https://Stackoverflow.com/users/644682', 'pm_score': 5, 'selected': False, 'text': '<p>Here is my version of Jeff\'s code. I\'ve made the following changes:</p>\n\n<ul>\n<li>The hyphens were appended in such a way that one could be added, and then need removing as it was the last character in the string. That is, we never want “my-slug-”. This means an extra string allocation to remove it on this edge case. I’ve worked around this by delay-hyphening. If you compare my code to Jeff’s the logic for this is easy to follow.</li>\n<li>His approach is purely lookup based and missed a lot of characters I found in examples while researching on Stack&nbsp;Overflow. To counter this, I first peform a normalisation pass (AKA collation mentioned in Meta Stack Overflow question <em><a href="https://meta.stackexchange.com/questions/7435/non-us-ascii-characters-dropped-from-full-profile-url/7696#7696">Non US-ASCII characters dropped from full (profile) URL</a></em>), and then ignore any characters outside the acceptable ranges. This works most of the time...</li>\n<li>... For when it doesn’t I’ve also had to add a lookup table. As mentioned above, some characters don’t map to a low ASCII value when normalised. Rather than drop these I’ve got a manual list of exceptions that is doubtless full of holes, but it is better than nothing. The normalisation code was inspired by Jon Hanna’s great post in Stack Overflow question <em><a href="https://stackoverflow.com/questions/3769457">How can I remove accents on a string?</a></em>.</li>\n<li><p>The case conversion is now also optional.</p>\n\n<pre><code>public static class Slug\n{\n    public static string Create(bool toLower, params string[] values)\n    {\n        return Create(toLower, String.Join("-", values));\n    }\n\n    /// &lt;summary&gt;\n    /// Creates a slug.\n    /// References:\n    /// http://www.unicode.org/reports/tr15/tr15-34.html\n    /// https://meta.stackexchange.com/questions/7435/non-us-ascii-characters-dropped-from-full-profile-url/7696#7696\n    /// https://stackoverflow.com/questions/25259/how-do-you-include-a-webpage-title-as-part-of-a-webpage-url/25486#25486\n    /// https://stackoverflow.com/questions/3769457/how-can-i-remove-accents-on-a-string\n    /// &lt;/summary&gt;\n    /// &lt;param name="toLower"&gt;&lt;/param&gt;\n    /// &lt;param name="normalised"&gt;&lt;/param&gt;\n    /// &lt;returns&gt;&lt;/returns&gt;\n    public static string Create(bool toLower, string value)\n    {\n        if (value == null)\n            return "";\n\n        var normalised = value.Normalize(NormalizationForm.FormKD);\n\n        const int maxlen = 80;\n        int len = normalised.Length;\n        bool prevDash = false;\n        var sb = new StringBuilder(len);\n        char c;\n\n        for (int i = 0; i &lt; len; i++)\n        {\n            c = normalised[i];\n            if ((c &gt;= \'a\' &amp;&amp; c &lt;= \'z\') || (c &gt;= \'0\' &amp;&amp; c &lt;= \'9\'))\n            {\n                if (prevDash)\n                {\n                    sb.Append(\'-\');\n                    prevDash = false;\n                }\n                sb.Append(c);\n            }\n            else if (c &gt;= \'A\' &amp;&amp; c &lt;= \'Z\')\n            {\n                if (prevDash)\n                {\n                    sb.Append(\'-\');\n                    prevDash = false;\n                }\n                // Tricky way to convert to lowercase\n                if (toLower)\n                    sb.Append((char)(c | 32));\n                else\n                    sb.Append(c);\n            }\n            else if (c == \' \' || c == \',\' || c == \'.\' || c == \'/\' || c == \'\\\\\' || c == \'-\' || c == \'_\' || c == \'=\')\n            {\n                if (!prevDash &amp;&amp; sb.Length &gt; 0)\n                {\n                    prevDash = true;\n                }\n            }\n            else\n            {\n                string swap = ConvertEdgeCases(c, toLower);\n\n                if (swap != null)\n                {\n                    if (prevDash)\n                    {\n                        sb.Append(\'-\');\n                        prevDash = false;\n                    }\n                    sb.Append(swap);\n                }\n            }\n\n            if (sb.Length == maxlen)\n                break;\n        }\n        return sb.ToString();\n    }\n\n    static string ConvertEdgeCases(char c, bool toLower)\n    {\n        string swap = null;\n        switch (c)\n        {\n            case \'ı\':\n                swap = "i";\n                break;\n            case \'ł\':\n                swap = "l";\n                break;\n            case \'Ł\':\n                swap = toLower ? "l" : "L";\n                break;\n            case \'đ\':\n                swap = "d";\n                break;\n            case \'ß\':\n                swap = "ss";\n                break;\n            case \'ø\':\n                swap = "o";\n                break;\n            case \'Þ\':\n                swap = "th";\n                break;\n        }\n        return swap;\n    }\n}\n</code></pre></li>\n</ul>\n\n<p>For more details, the unit tests, and an explanation of why <a href="http://en.wikipedia.org/wiki/Facebook" rel="noreferrer">Facebook</a>\'s <a href="http://en.wikipedia.org/wiki/Uniform_Resource_Locator" rel="noreferrer">URL</a> scheme is a little smarter than Stack Overflows, I\'ve got an <a href="http://www.danharman.net/2011/07/18/seo-slugification-in-dotnet-aka-unicode-to-ascii-aka-diacritic-stripping/" rel="noreferrer">expanded version of this on my blog</a>.</p>\n'}, {'answer_id': 9898628, 'author': 'Peyman Mehrabani', 'author_id': 1138725, 'author_profile': 'https://Stackoverflow.com/users/1138725', 'pm_score': 2, 'selected': False, 'text': '<p>You can use the following helper method. It can convert the Unicode characters.</p>\n\n<pre><code>public static string ConvertTextToSlug(string s)\n{\n    StringBuilder sb = new StringBuilder();\n\n    bool wasHyphen = true;\n\n    foreach (char c in s)\n    {\n        if (char.IsLetterOrDigit(c))\n        {\n            sb.Append(char.ToLower(c));\n            wasHyphen = false;\n        }\n        else\n            if (char.IsWhiteSpace(c) &amp;&amp; !wasHyphen)\n            {\n                sb.Append(\'-\');\n                wasHyphen = true;\n            }\n    }\n\n    // Avoid trailing hyphens\n    if (wasHyphen &amp;&amp; sb.Length &gt; 0)\n        sb.Length--;\n\n    return sb.ToString().Replace("--","-");\n}\n</code></pre>\n'}, {'answer_id': 20423735, 'author': 'Community', 'author_id': -1, 'author_profile': 'https://Stackoverflow.com/users/-1', 'pm_score': 1, 'selected': False, 'text': '<p>I liked the way this is done without using <a href="http://en.wikipedia.org/wiki/Regular_expression" rel="nofollow">regular expressions</a>, so I ported it to PHP. I just added a function called <code>is_between</code> to check characters:</p>\n\n<pre><code>function is_between($val, $min, $max)\n{\n    $val = (int) $val; $min = (int) $min; $max = (int) $max;\n\n    return ($val &gt;= $min &amp;&amp; $val &lt;= $max);\n}\n\nfunction international_char_to_ascii($char)\n{\n    if (mb_strpos(\'àåáâäãåa\', $char) !== false)\n    {\n        return \'a\';\n    }\n\n    if (mb_strpos(\'èéêëe\', $char) !== false)\n    {\n        return \'e\';\n    }\n\n    if (mb_strpos(\'ìíîïi\', $char) !== false)\n    {\n        return \'i\';\n    }\n\n    if (mb_strpos(\'òóôõö\', $char) !== false)\n    {\n        return \'o\';\n    }\n\n    if (mb_strpos(\'ùúûüuu\', $char) !== false)\n    {\n        return \'u\';\n    }\n\n    if (mb_strpos(\'çccc\', $char) !== false)\n    {\n        return \'c\';\n    }\n\n    if (mb_strpos(\'zzž\', $char) !== false)\n    {\n        return \'z\';\n    }\n\n    if (mb_strpos(\'ssšs\', $char) !== false)\n    {\n        return \'s\';\n    }\n\n    if (mb_strpos(\'ñn\', $char) !== false)\n    {\n        return \'n\';\n    }\n\n    if (mb_strpos(\'ýÿ\', $char) !== false)\n    {\n        return \'y\';\n    }\n\n    if (mb_strpos(\'gg\', $char) !== false)\n    {\n        return \'g\';\n    }\n\n    if (mb_strpos(\'r\', $char) !== false)\n    {\n        return \'r\';\n    }\n\n    if (mb_strpos(\'l\', $char) !== false)\n    {\n        return \'l\';\n    }\n\n    if (mb_strpos(\'d\', $char) !== false)\n    {\n        return \'d\';\n    }\n\n    if (mb_strpos(\'ß\', $char) !== false)\n    {\n        return \'ss\';\n    }\n\n    if (mb_strpos(\'Þ\', $char) !== false)\n    {\n        return \'th\';\n    }\n\n    if (mb_strpos(\'h\', $char) !== false)\n    {\n        return \'h\';\n    }\n\n    if (mb_strpos(\'j\', $char) !== false)\n    {\n        return \'j\';\n    }\n    return \'\';\n}\n\nfunction url_friendly_title($url_title)\n{\n    if (empty($url_title))\n    {\n        return \'\';\n    }\n\n    $url_title = mb_strtolower($url_title);\n\n    $url_title_max_length   = 80;\n    $url_title_length       = mb_strlen($url_title);\n    $url_title_friendly     = \'\';\n    $url_title_dash_added   = false;\n    $url_title_char = \'\';\n\n    for ($i = 0; $i &lt; $url_title_length; $i++)\n    {\n        $url_title_char     = mb_substr($url_title, $i, 1);\n\n        if (strlen($url_title_char) == 2)\n        {\n            $url_title_ascii    = ord($url_title_char[0]) * 256 + ord($url_title_char[1]) . "\\r\\n";\n        }\n        else\n        {\n            $url_title_ascii    = ord($url_title_char);\n        }\n\n        if (is_between($url_title_ascii, 97, 122) || is_between($url_title_ascii, 48, 57))\n        {\n            $url_title_friendly .= $url_title_char;\n\n            $url_title_dash_added = false;\n        }\n        elseif(is_between($url_title_ascii, 65, 90))\n        {\n            $url_title_friendly .= chr(($url_title_ascii | 32));\n\n            $url_title_dash_added = false;\n        }\n        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)\n        {\n            if (!$url_title_dash_added &amp;&amp; mb_strlen($url_title_friendly) &gt; 0)\n            {\n                $url_title_friendly .= chr(45);\n\n                $url_title_dash_added = true;\n            }\n        }\n        else if ($url_title_ascii &gt;= 128)\n        {\n            $url_title_previous_length = mb_strlen($url_title_friendly);\n\n            $url_title_friendly .= international_char_to_ascii($url_title_char);\n\n            if ($url_title_previous_length != mb_strlen($url_title_friendly))\n            {\n                $url_title_dash_added = false;\n            }\n        }\n\n        if ($i == $url_title_max_length)\n        {\n            break;\n        }\n    }\n\n    if ($url_title_dash_added)\n    {\n        return mb_substr($url_title_friendly, 0, -1);\n    }\n    else\n    {\n        return $url_title_friendly;\n    }\n}\n</code></pre>\n'}, {'answer_id': 25063322, 'author': 'giammin', 'author_id': 921690, 'author_profile': 'https://Stackoverflow.com/users/921690', 'pm_score': 2, 'selected': False, 'text': '<p>The <a href="https://stackoverflow.com/questions/25259/how-does-stack-overflow-generate-its-seo-friendly-urls/25486#25486">stackoverflow solution</a> is great, but modern browser (excluding IE, as usual) now handle nicely utf8 encoding:</p>\n\n<p><img src="https://i.stack.imgur.com/cYWiT.jpg" alt="enter image description here"></p>\n\n<p>So I upgraded the proposed solution:</p>\n\n<pre><code>public static string ToFriendlyUrl(string title, bool useUTF8Encoding = false)\n{\n    ...\n\n        else if (c &gt;= 128)\n        {\n            int prevlen = sb.Length;\n            if (useUTF8Encoding )\n            {\n                sb.Append(HttpUtility.UrlEncode(c.ToString(CultureInfo.InvariantCulture),Encoding.UTF8));\n            }\n            else\n            {\n                sb.Append(RemapInternationalCharToAscii(c));\n            }\n    ...\n}\n</code></pre>\n\n<p><a href="http://pastebin.com/cANXF8eV" rel="nofollow noreferrer">Full Code on Pastebin</a></p>\n\n<p>Edit: <a href="https://meta.stackexchange.com/a/7696/245495">Here\'s the code</a> for <code>RemapInternationalCharToAscii</code> method (that\'s missing in the pastebin).</p>\n'}, {'answer_id': 29551373, 'author': 'Ronnie Overby', 'author_id': 64334, 'author_profile': 'https://Stackoverflow.com/users/64334', 'pm_score': 2, 'selected': False, 'text': '<p>Here\'s my (slower, but fun to write) version of Jeff\'s code:</p>\n\n<pre><code>public static string URLFriendly(string title)\n{\n    char? prevRead = null,\n        prevWritten = null;\n\n    var seq = \n        from c in title\n        let norm = RemapInternationalCharToAscii(char.ToLowerInvariant(c).ToString())[0]\n        let keep = char.IsLetterOrDigit(norm)\n        where prevRead.HasValue || keep\n        let replaced = keep ? norm\n            :  prevWritten != \'-\' ? \'-\'\n            :  (char?)null\n        where replaced != null\n        let s = replaced + (prevRead == null ? ""\n            : norm == \'#\' &amp;&amp; "cf".Contains(prevRead.Value) ? "sharp"\n            : norm == \'+\' ? "plus"\n            : "")\n        let _ = prevRead = norm\n        from written in s\n        let __ = prevWritten = written\n        select written;\n\n    const int maxlen = 80;  \n    return string.Concat(seq.Take(maxlen)).TrimEnd(\'-\');\n}\n\npublic static string RemapInternationalCharToAscii(string text)\n{\n    var seq = text.Normalize(NormalizationForm.FormD)\n        .Where(c =&gt; CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark);\n\n    return string.Concat(seq).Normalize(NormalizationForm.FormC);\n}\n</code></pre>\n\n<p>My test string:</p>\n\n<p><code>"  I love C#, F#, C++, and... Crème brûlée!!! They see me codin\'... they hatin\'... tryin\' to catch me codin\' dirty... "</code></p>\n'}, {'answer_id': 30242890, 'author': 'ikourfaln', 'author_id': 4780866, 'author_profile': 'https://Stackoverflow.com/users/4780866', 'pm_score': 1, 'selected': False, 'text': '<p>Now all Browser handle nicely utf8 encoding, so you can use <a href="https://msdn.microsoft.com/en-us/library/system.net.webutility.urlencode%28v=vs.110%29.aspx" rel="nofollow">WebUtility.UrlEncode</a> Method , its like <a href="https://msdn.microsoft.com/en-us/library/h10z5byc%28v=vs.110%29.aspx" rel="nofollow">HttpUtility.UrlEncode</a> used by @giamin but its work outside of a web application.</p>\n'}, {'answer_id': 32794641, 'author': 'Rotem', 'author_id': 628659, 'author_profile': 'https://Stackoverflow.com/users/628659', 'pm_score': 2, 'selected': False, 'text': '<p>I know it\'s very old question but since most of the browsers now <strong>support unicode urls</strong> I found a great solution in <strong>XRegex</strong> that converts everything except letters (in all languages to \'-\').</p>\n\n<p>That can be done in several programming languages.</p>\n\n<p>The pattern is <code>\\\\p{^L}+</code> and then you just need to use it to replace all non letters to \'-\'.</p>\n\n<p>Working example in node.js with <a href="https://www.npmjs.com/package/xregexp" rel="nofollow">xregex</a> module.</p>\n\n<pre><code>var text = \'This ! can @ have # several $ letters % from different languages such as עברית or Español\';\n\nvar slugRegEx = XRegExp(\'((?!\\\\d)\\\\p{^L})+\', \'g\');\n\nvar slug = XRegExp.replace(text, slugRegEx, \'-\').toLowerCase();\n\nconsole.log(slug) ==&gt; "this-can-have-several-letters-from-different-languages-such-as-עברית-or-español"\n</code></pre>\n'}, {'answer_id': 49909421, 'author': 'Sam', 'author_id': 2138442, 'author_profile': 'https://Stackoverflow.com/users/2138442', 'pm_score': 1, 'selected': False, 'text': '<p>I ported the code to TypeScript. It can easily be adapted to JavaScript.</p>\n\n<p>I am adding a <code>.contains</code> method to the <code>String</code> prototype, if you\'re targeting the latest browsers or ES6 you can use <code>.includes</code> instead.</p>\n\n<pre><code>if (!String.prototype.contains) {\n    String.prototype.contains = function (check) {\n        return this.indexOf(check, 0) !== -1;\n    };\n}\n\ndeclare interface String {\n    contains(check: string): boolean;\n}\n\nexport function MakeUrlFriendly(title: string) {\n            if (title == null || title == \'\')\n                return \'\';\n\n            const maxlen = 80;\n            let len = title.length;\n            let prevdash = false;\n            let result = \'\';\n            let c: string;\n            let cc: number;\n            let remapInternationalCharToAscii = function (c: string) {\n                let s = c.toLowerCase();\n                if ("àåáâäãåą".contains(s)) {\n                    return "a";\n                }\n                else if ("èéêëę".contains(s)) {\n                    return "e";\n                }\n                else if ("ìíîïı".contains(s)) {\n                    return "i";\n                }\n                else if ("òóôõöøőð".contains(s)) {\n                    return "o";\n                }\n                else if ("ùúûüŭů".contains(s)) {\n                    return "u";\n                }\n                else if ("çćčĉ".contains(s)) {\n                    return "c";\n                }\n                else if ("żźž".contains(s)) {\n                    return "z";\n                }\n                else if ("śşšŝ".contains(s)) {\n                    return "s";\n                }\n                else if ("ñń".contains(s)) {\n                    return "n";\n                }\n                else if ("ýÿ".contains(s)) {\n                    return "y";\n                }\n                else if ("ğĝ".contains(s)) {\n                    return "g";\n                }\n                else if (c == \'ř\') {\n                    return "r";\n                }\n                else if (c == \'ł\') {\n                    return "l";\n                }\n                else if (c == \'đ\') {\n                    return "d";\n                }\n                else if (c == \'ß\') {\n                    return "ss";\n                }\n                else if (c == \'Þ\') {\n                    return "th";\n                }\n                else if (c == \'ĥ\') {\n                    return "h";\n                }\n                else if (c == \'ĵ\') {\n                    return "j";\n                }\n                else {\n                    return "";\n                }\n            };\n\n            for (let i = 0; i &lt; len; i++) {\n                c = title[i];\n                cc = c.charCodeAt(0);\n\n                if ((cc &gt;= 97 /* a */ &amp;&amp; cc &lt;= 122 /* z */) || (cc &gt;= 48 /* 0 */ &amp;&amp; cc &lt;= 57 /* 9 */)) {\n                    result += c;\n                    prevdash = false;\n                }\n                else if ((cc &gt;= 65 &amp;&amp; cc &lt;= 90 /* A - Z */)) {\n                    result += c.toLowerCase();\n                    prevdash = false;\n                }\n                else if (c == \' \' || c == \',\' || c == \'.\' || c == \'/\' || c == \'\\\\\' || c == \'-\' || c == \'_\' || c == \'=\') {\n                    if (!prevdash &amp;&amp; result.length &gt; 0) {\n                        result += \'-\';\n                        prevdash = true;\n                    }\n                }\n                else if (cc &gt;= 128) {\n                    let prevlen = result.length;\n                    result += remapInternationalCharToAscii(c);\n                    if (prevlen != result.length) prevdash = false;\n                }\n                if (i == maxlen) break;\n            }\n\n            if (prevdash)\n                return result.substring(0, result.length - 1);\n            else\n                return result;\n        }\n</code></pre>\n'}, {'answer_id': 63829335, 'author': 'David', 'author_id': 817565, 'author_profile': 'https://Stackoverflow.com/users/817565', 'pm_score': -1, 'selected': False, 'text': "<p>Rewrite of Jeff's code to be more concise</p>\n<pre><code>    public static string RemapInternationalCharToAscii(char c)\n    {\n        var s = c.ToString().ToLowerInvariant();\n\n        var mappings = new Dictionary&lt;string, string&gt;\n        {\n            { &quot;a&quot;, &quot;àåáâäãåą&quot; },\n            { &quot;c&quot;, &quot;çćčĉ&quot; },\n            { &quot;d&quot;, &quot;đ&quot; },\n            { &quot;e&quot;, &quot;èéêëę&quot; },\n            { &quot;g&quot;, &quot;ğĝ&quot; },\n            { &quot;h&quot;, &quot;ĥ&quot; },\n            { &quot;i&quot;, &quot;ìíîïı&quot; },\n            { &quot;j&quot;, &quot;ĵ&quot; },\n            { &quot;l&quot;, &quot;ł&quot; },\n            { &quot;n&quot;, &quot;ñń&quot; },\n            { &quot;o&quot;, &quot;òóôõöøőð&quot; },\n            { &quot;r&quot;, &quot;ř&quot; },\n            { &quot;s&quot;, &quot;śşšŝ&quot; },\n            { &quot;ss&quot;, &quot;ß&quot; },\n            { &quot;th&quot;, &quot;Þ&quot; },\n            { &quot;u&quot;, &quot;ùúûüŭů&quot; },\n            { &quot;y&quot;, &quot;ýÿ&quot; },\n            { &quot;z&quot;, &quot;żźž&quot; }\n        };\n\n        foreach(var mapping in mappings)\n        {\n            if (mapping.Value.Contains(s))\n                return mapping.Key;\n        }\n\n        return string.Empty;\n    }\n</code></pre>\n"}]
['https://Stackoverflow.com/questions/25259', 'https://Stackoverflow.com', 'https://Stackoverflow.com/users/1632/']