40

I am trying to truncate a long string of text to a certain length, but want to also make sure that the truncated result ends at a whitespace. I am also going to append an ellipsis afterwards.

For example this:

"This is a very long string that has more characters than I want in it."

becomes this:

"This is a very long string that..."

I am starting with this but obviously this doesn't deal with the problem of ending the string on whitespace.

<%= item.description[0..30] %>&hellip;
pagid
  • 13,559
  • 11
  • 78
  • 104
e_r
  • 794
  • 2
  • 8
  • 18

6 Answers6

49

If you're using Rails 4+ you should just use the built-in truncate helper method, e.g.:

<%= truncate item.description, length: 30, separator: /\w+/ %>

The string "…" will be appended to truncated text; to specify a different string, use the :omission option, e.g. omission: "xxx".

For Rails 3.x the :separator option must be a string. Giving :separator => " " will be fine in many cases, but only catches spaces and not other whitespace. One compromise would be to use String#squish, which replaces all sequences of whitespace with a single space (and also trims leading and trailing whitespace), e.g. "foo\n\tbar ".squish yields "foo bar". It would look like this:

<%= truncate item.description.squish, :length => 30, :separator => /\w/,
                                      :omission => "&hellip;" %>
Jordan Running
  • 102,619
  • 17
  • 182
  • 182
48
s[0..30].gsub(/\s\w+\s*$/, '...')

The original answer didn't work in the case where the 30 character substring ended on a whitespace character. This solves that.

>> desc="This is some text it is really long"

>> desc[0..30].gsub(/\s\w+$/,'...')
"This is some text it is really "

>> desc[0..30].gsub(/\s\w+\s*$/,'...')
"This is some text it is..."
user664833
  • 18,397
  • 19
  • 91
  • 140
evfwcqcg
  • 15,755
  • 15
  • 55
  • 72
  • Thanks for the pure ruby answer! Worked perfectly. – e_r Feb 29 '12 at 17:28
  • 6
    This is not quite what most people want. It adds the ellipsis regardless of whether the string is actually longer than 30 characters. – mostlydev Mar 29 '16 at 13:12
  • Also this answer throws out the last word for no reason, even it there is no need for it. Woudn't be the string "This is some text it is really..." be more expected outcome? – gorn Oct 06 '16 at 20:39
  • Three periods is not the same as an ellipsis. Please, where you can, use a Unicode ellipsis character, not three periods. – Robin Daugherty Jan 27 '19 at 21:27
8

@evfwcqcg's answer is very good. I found it did not work well when

  1. The string contained other characters that are non-space not alphanumerical.
  2. The string is shorter than the desired length.

Demonstration:

>> s = "How about we put some ruby method Class#Method in our string"
=> "How about we put some ruby method Class#Method in our string"
>> s[0..41].gsub(/\s\w+\s*$/, '...')
=> "How about we put some ruby method Class#Me"
>> s[0..999].gsub(/\s\w+\s*$/, '...')
=> "How about we put some ruby method Class#Method in our..."

This is not what I expected.

Here is what I am using to fix this:

def truncate s, length = 30, ellipsis = '...'
  if s.length > length
    s.to_s[0..length].gsub(/[^\w]\w+\s*$/, ellipsis)
  else
    s
  end
end

When doing tests, here is the output:

>> s = "This is some text it is really long"
=> "This is some text it is really long"
>> truncate s
=> "This is some text it is..."

Still behave as expected.

>> s = "How about we put some ruby method Class#Method in our string"
=> "How about we put some ruby method Class#Method in our string"
>> truncate s, 41
=> "How about we put some ruby method Class..."
>> truncate s, 999
=> "How about we put some ruby method Class#Method in our string"

This is more like it.

roychri
  • 2,866
  • 1
  • 21
  • 29
  • Although this is better than @evfwcqcg's answer still I do not know why you would like to take out last word even if it fits in to the limit. In case of your exmple - why you see the text "This is some text it is" as desired result when "This is some text it is really" has 30 characters and it ends at whitespace as well. – gorn Oct 06 '16 at 20:51
  • @gorn Because once you add the ellipsis, it's going to be over 30 chars. Granted, if the ellipsis's length is over the last word's length, its going to be over 30 characters anyway. But in most cases, with the ellipsis being "...", it would only go over 30 if the last word is 1 character, which I think does not happen very often. Do you have any suggestions to make this better? – roychri Oct 07 '16 at 02:09
  • I understand that it will "work in most cases", so my ranting has more academic than practical value, but thats the way I am when I see "incorrect" code. Sorry about that. I have posted a solution as a separate answer, because it got too long for a comment. Feel free to add your thoughts there. – gorn Oct 07 '16 at 08:12
  • An ellipsis is only one character long, if you use the Unicode ellipsis character. Please do. – Robin Daugherty Jan 27 '19 at 21:37
3
desc.gsub(/([\w\s]{30}).+/,'\1...')

Expanding on the answer by @evfwcqcg, this is a pure regex that solves the problem of trailing whitespace.

irb(main):031:0> desc="This is some text it is really long"
irb(main):033:0> desc.gsub(/([\w\s]{30}).+/,'\1...')
=> "This is some text it is really..."
irb(main):034:0> desc="This is some text it is really"
=> "This is some text it is really"
irb(main):035:0> desc.gsub(/([\w\s]{30}).+/,'\1...')
=> "This is some text it is really"
irb(main):036:0> desc="This is some text it is real"
=> "This is some text it is real"
irb(main):037:0> desc.gsub(/([\w\s]{30}).+/,'\1...')
=> "This is some text it is real"
Ethan
  • 54
  • 3
  • This is plain wrong solution. If you try with the original string "This is a very long string that has more characters than I want in it." than you get "This is a very long string tha..." ... – gorn Oct 06 '16 at 20:56
3

I am surprised that none of the answers is really correct (or limited by using rails helper) although this is very old question, so here is the solution.

Lets clearly formulate what it the goal first. We want truncate string s to 30 characters and cut the last word out as well if it can not entirely fit in. We also want to truncate trailing spaces from the result and add ellipsis, if the text was shortened.

If the text is longer then limit, than the shortening is as easy as

s[0,s.rindex(/\s/,30)].rstrip + '...'

If we wanted the entire result to be max 30 characters, than it is as simple as subtracting the length of ellipse from 30. So because we use three dots (and not one three-dot character) than we need

s[0,s.rindex(/\s/,27)].rstrip + '...'

And the final result (with the test whether we need to truncate at all) is:

if s.length<=30
  s
else
  s[0,s.rindex(/\s/,27)].rstrip + '...'
end

Thats it.


Note: There are some shady cases, when the desired result is not obvious. Here they are:

  • If the string ends with lots of spaces (s= "Helo word ") but is shorter than 30. Should the spaces be preserved? - Currently they are.
  • The same as above, but the spaces at the end cross the limit o 30. Like in (s= "Twentyseven chars long text ") - Currently all spaces ad the end are truncated and ellipsis added.
gorn
  • 5,042
  • 7
  • 31
  • 46
0
class String
  def trunca(length=100, ellipsis='...')
    self.length > length ? self[0..length].gsub(/\s*\S*\z/, '').rstrip+ellipsis : self.rstrip
  end
end

Example:

-bash> irb
2.0.0p247 :001 > class String
2.0.0p247 :002?>     def trunca(length=100, ellipsis='...')
2.0.0p247 :003?>         self.length > length ? self[0..length].gsub(/\s*\S*\z/, '').rstrip+ellipsis : self.rstrip
2.0.0p247 :004?>       end
2.0.0p247 :005?>   end
 => nil 
2.0.0p247 :006 > s = "This is a very long string that has more characters than I want to display."
 => "This is a very long string that has more characters than I want to display." 
2.0.0p247 :007 > s.trunca(20)
 => "This is a very long..." 
2.0.0p247 :008 > s.trunca(31)
 => "This is a very long string that..." 
user664833
  • 18,397
  • 19
  • 91
  • 140