├── .gitignore ├── .travis.yml ├── Gemfile ├── Gemfile.lock ├── History.txt ├── LICENSE ├── README.md ├── Rakefile ├── VERSION ├── init.rb ├── lib ├── app │ └── helpers │ │ └── truncate_html_helper.rb ├── truncate_html.rb └── truncate_html │ ├── configuration.rb │ ├── html_string.rb │ ├── html_truncator.rb │ └── version.rb ├── spec ├── helpers │ └── truncate_html_helper_spec.rb ├── rails_root │ ├── .bundle │ │ └── config │ ├── Gemfile │ ├── Gemfile.lock │ ├── app │ │ ├── controllers │ │ │ └── application_controller.rb │ │ └── helpers │ │ │ └── application_helper.rb │ ├── config │ │ ├── application.rb │ │ ├── boot.rb │ │ ├── database.yml │ │ ├── environment.rb │ │ ├── environments │ │ │ ├── development.rb │ │ │ ├── production.rb │ │ │ └── test.rb │ │ ├── initializers │ │ │ ├── backtrace_silencers.rb │ │ │ ├── inflections.rb │ │ │ ├── mime_types.rb │ │ │ ├── new_rails_defaults.rb │ │ │ └── session_store.rb │ │ ├── locales │ │ │ └── en.yml │ │ └── routes.rb │ ├── init.rb │ └── lib │ │ ├── app │ │ └── helpers │ │ │ └── truncate_html_helper.rb │ │ └── tasks │ │ └── rspec.rake ├── spec.opts ├── spec_helper.rb └── truncate_html │ ├── configuration_spec.rb │ ├── html_string_spec.rb │ ├── html_truncator_spec.rb │ └── truncate_html_spec.rb └── truncate_html.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | pkg 2 | coverage 3 | profiling 4 | tmp 5 | spec/rails_root/log/* 6 | log/*.log 7 | .bundle 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 1.9.2 4 | - 1.9.3 5 | - 2.0.0 6 | - jruby-19mode # JRuby in 1.9 mode 7 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | # Specify your gem's dependencies in truncate_html.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | truncate_html (0.9.3) 5 | 6 | GEM 7 | remote: http://rubygems.org/ 8 | specs: 9 | actionmailer (3.2.13) 10 | actionpack (= 3.2.13) 11 | mail (~> 2.5.3) 12 | actionpack (3.2.13) 13 | activemodel (= 3.2.13) 14 | activesupport (= 3.2.13) 15 | builder (~> 3.0.0) 16 | erubis (~> 2.7.0) 17 | journey (~> 1.0.4) 18 | rack (~> 1.4.5) 19 | rack-cache (~> 1.2) 20 | rack-test (~> 0.6.1) 21 | sprockets (~> 2.2.1) 22 | activemodel (3.2.13) 23 | activesupport (= 3.2.13) 24 | builder (~> 3.0.0) 25 | activerecord (3.2.13) 26 | activemodel (= 3.2.13) 27 | activesupport (= 3.2.13) 28 | arel (~> 3.0.2) 29 | tzinfo (~> 0.3.29) 30 | activeresource (3.2.13) 31 | activemodel (= 3.2.13) 32 | activesupport (= 3.2.13) 33 | activesupport (3.2.13) 34 | i18n (= 0.6.1) 35 | multi_json (~> 1.0) 36 | arel (3.0.2) 37 | builder (3.0.4) 38 | diff-lcs (1.2.2) 39 | erubis (2.7.0) 40 | hike (1.2.1) 41 | i18n (0.6.1) 42 | journey (1.0.4) 43 | json (1.7.7) 44 | mail (2.5.3) 45 | i18n (>= 0.4.0) 46 | mime-types (~> 1.16) 47 | treetop (~> 1.4.8) 48 | mime-types (1.22) 49 | multi_json (1.7.2) 50 | polyglot (0.3.3) 51 | rack (1.4.5) 52 | rack-cache (1.2) 53 | rack (>= 0.4) 54 | rack-ssl (1.3.3) 55 | rack 56 | rack-test (0.6.2) 57 | rack (>= 1.0) 58 | rails (3.2.13) 59 | actionmailer (= 3.2.13) 60 | actionpack (= 3.2.13) 61 | activerecord (= 3.2.13) 62 | activeresource (= 3.2.13) 63 | activesupport (= 3.2.13) 64 | bundler (~> 1.0) 65 | railties (= 3.2.13) 66 | railties (3.2.13) 67 | actionpack (= 3.2.13) 68 | activesupport (= 3.2.13) 69 | rack-ssl (~> 1.3.2) 70 | rake (>= 0.8.7) 71 | rdoc (~> 3.4) 72 | thor (>= 0.14.6, < 2.0) 73 | rake (10.0.4) 74 | rdoc (3.12.2) 75 | json (~> 1.4) 76 | rspec-core (2.13.1) 77 | rspec-expectations (2.13.0) 78 | diff-lcs (>= 1.1.3, < 2.0) 79 | rspec-mocks (2.13.0) 80 | rspec-rails (2.13.0) 81 | actionpack (>= 3.0) 82 | activesupport (>= 3.0) 83 | railties (>= 3.0) 84 | rspec-core (~> 2.13.0) 85 | rspec-expectations (~> 2.13.0) 86 | rspec-mocks (~> 2.13.0) 87 | sprockets (2.2.2) 88 | hike (~> 1.2) 89 | multi_json (~> 1.0) 90 | rack (~> 1.0) 91 | tilt (~> 1.1, != 1.3.0) 92 | thor (0.18.1) 93 | tilt (1.3.6) 94 | treetop (1.4.12) 95 | polyglot 96 | polyglot (>= 0.3.1) 97 | tzinfo (0.3.37) 98 | 99 | PLATFORMS 100 | ruby 101 | 102 | DEPENDENCIES 103 | rails (~> 3.2.13) 104 | rspec-rails (~> 2.13) 105 | truncate_html! 106 | -------------------------------------------------------------------------------- /History.txt: -------------------------------------------------------------------------------- 1 | == 0.5.1 2011-04-08 2 | * Ensure resulting string's length is never greater than supplied length (csquared) 3 | 4 | == 0.5.0 2011-01-26 5 | * Multibyte support. (smix, parndt) 6 | 7 | == 0.4.0 2010-03-30 8 | * Rails 3 support. This breaks rails 2 support. 9 | 10 | == 0.3.2 2010-03-23 11 | * Fix for autoloading of classes in older Rails versions. (kball) 12 | * Fix issue #5: autoloading of default configuration. 13 | 14 | == 0.3.1 2010-02-03 15 | * Fixed minor typo on the word_boundary option name. 16 | 17 | == 0.3.0 2010-02-02 18 | * Added the ability to set global configuration parameters 19 | * Added the word_boundry option 20 | 21 | == 0.2.2 2009-12-23 22 | * Fix issue #4: Handle case when supplied length is smaller than omission. (ghazel) 23 | 24 | == 0.2.1 2009-12-18 25 | * Fix issue #3: Handle case when input html contins a script tag. 26 | 27 | == 0.2.0 2009-11-23 28 | * Fix issue #2: The omission text's length is now included in the returned 29 | string's calculation. This is more consistent with the rails truncate 30 | helper's behavior. 31 | 32 | == 0.1.2 2009-09-25 33 | * Fix issue #1: Handle case when input html is nil. (bcardarella) 34 | 35 | == 0.1.1 2009-08-25 36 | * Fixed issue with regex which would not recognize tags that contain slashes. 37 | * Other refactoring and improvements to spec coverage. 38 | 39 | == 0.1.0 2009-08-03 40 | * Wrote truncate_html. Initial release. 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2009 - 2010 Harold A. Giménez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | TruncateHtml 2 | ============ 3 | 4 | [![Build Status](https://secure.travis-ci.org/hgmnz/truncate_html.svg?branch=master)](http://travis-ci.org/hgmnz/truncate_html) 5 | [![Code Climate](https://codeclimate.com/github/hgmnz/truncate_html.svg)](https://codeclimate.com/github/hgmnz/truncate_html) 6 | 7 | truncate_html cuts off a string of HTML and takes care of closing any lingering open tags. There are many ways to solve this. This library does not have any dependencies, and [parses HTML using regular expressions](http://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags/1732454#1732454). 8 | 9 | It can be used with or without Rails. 10 | 11 | Example 12 | ------- 13 | 14 | ```ruby 15 | some_html = '' 16 | truncate_html(some_html, length: 15, omission: '...(continued)') 17 | => 18 | ``` 19 | 20 | A few notes: 21 | 22 | * By default, it will truncate on word boundary. 23 | To truncate the HTML string strictly at the specified length, pass in the `word_boundary: false` option. 24 | * If the input HTML is nil, it will return an empty string. 25 | * The omission text's length does count toward the resulting string's length. 26 | * ` and more text

" 71 | expected_out = "

I have a script and...

" 72 | truncate(input_html, :length => 23).should == expected_out 73 | end 74 | 75 | it 'in the middle of a link, truncates and closes the , and closes any remaining open tags' do 76 | html = '
' 77 | expected = '
' 78 | truncate(html, :length => 15).should == expected 79 | end 80 | 81 | %w(! @ # $ % ^ & * \( \) - _ + = [ ] { } \ | , . / ?).each do |char| 82 | context "when the html has a #{char} character after a closing tag" do 83 | it 'places the punctuation after the tag without any whitespace' do 84 | html = "

Look at this#{char} More words here

" 85 | expected = "

Look at this#{char}...

" 86 | truncate(html, :length => 19).should == expected 87 | end 88 | end 89 | end 90 | 91 | context 'when the html has a non punctuation character after a closing tag' do 92 | it 'leaves a whitespace between the closing tag and the following word character' do 93 | html = '

Look at this link for randomness

' 94 | expected = '

Look at this link...

' 95 | truncate(html, :length => 21).should == expected 96 | end 97 | end 98 | 99 | it 'handles multibyte characters and leaves them in the result' do 100 | html = '

Look at our multibyte characters ā ž this link for randomness ā ž

' 101 | truncate(html, :length => html.length).should == html 102 | end 103 | 104 | #unusual, but just covering my ass 105 | it 'recognizes the multiline html properly' do 106 | html = <<-END_HTML 107 |
109 | This is ugly html. 110 |
111 | END_HTML 112 | truncate(html, :length => 12).should == '
This is...
' 113 | end 114 | 115 | %w(br hr img).each do |unpaired_tag| 116 | context "when the html contains a #{unpaired_tag} tag" do 117 | 118 | context "and the #{unpaired_tag} does not have the closing slash" do 119 | it "does not close the #{unpaired_tag} tag" do 120 | html = "
Some before. <#{unpaired_tag}>and some after
" 121 | html_caps = "
Some before. <#{unpaired_tag.capitalize}>and some after
" 122 | truncate(html, :length => 19).should == "
Some before. <#{unpaired_tag}>and...
" 123 | truncate(html_caps, :length => 19).should == "
Some before. <#{unpaired_tag.capitalize}>and...
" 124 | end 125 | end 126 | 127 | context "and the #{unpaired_tag} does have the closing slash" do 128 | it "does not close the #{unpaired_tag} tag" do 129 | html = "
Some before. <#{unpaired_tag} />and some after
" 130 | html_caps = "
Some before. <#{unpaired_tag.capitalize} />and some after
" 131 | truncate(html, :length => 19).should == "
Some before. <#{unpaired_tag} />and...
" 132 | truncate(html_caps, :length => 19).should == "
Some before. <#{unpaired_tag.capitalize} />and...
" 133 | end 134 | end 135 | 136 | end 137 | end 138 | 139 | it 'does not truncate quotes off when input contains chinese characters' do 140 | html = "

“我现在使用的是中文的拼音。”
141 | 测试一下具体的truncatehtml功能。
142 | “我现在使用的是中文的拼音。”
143 | 测试一下具体的truncate
html功能。
144 | “我现在使用的是中文的拼音。”
145 | 测试一下具体的truncatehtml功能。
146 | “我现在使用的是中文的拼音。”
147 | 测试一下具体的truncate
html功能。

" 148 | 149 | result = truncate(html, :omission => "", :length => 50) 150 | result.should include "

“我现在使用的是中文的拼音。”
" 151 | end 152 | 153 | context 'when the break_token option is set as ' do 154 | it 'does not truncate abnormally if the break_token is not present' do 155 | truncate('This is line one. This is line two.', :length => 30, :break_token => '').should == 'This is line one. This is...' 156 | end 157 | it 'does not truncate abnormally if the break_token is present, but beyond the length param' do 158 | truncate('This is line one. This is line two.', :length => 30, :break_token => '').should == 'This is line one. This is...' 159 | end 160 | it 'truncates before the length param if the break_token is before the token at "length"' do 161 | truncate('This is line one. This is line two.', :length => 30, :break_token => '').should == 'This is line one.' 162 | end 163 | end 164 | 165 | context 'when the break_token option is customized as a comment' do 166 | it 'does not truncate abnormally if the break_token is not present' do 167 | truncate('This is line one. This is line two.', :length => 30, :break_token => '').should == 'This is line one. This is...' 168 | end 169 | it 'does not truncate abnormally if the break_token is present, but beyond the length param' do 170 | truncate('This is line one. This is line two.', :length => 30, :break_token => '').should == 'This is line one. This is...' 171 | end 172 | it 'truncates before the length param if the break_token is before the token at "length"' do 173 | truncate('This is line one. This is line two.', :length => 30, :break_token => '').should == 'This is line one.' 174 | end 175 | end 176 | 177 | context 'when the break_token option is customized as an html tag' do 178 | it 'does not truncate abnormally if the break_token is not present' do 179 | truncate('This is line one. This is line two.', :length => 30, :break_token => '').should == 'This is line one. This is...' 180 | end 181 | it 'does not truncate abnormally if the break_token is present, but beyond the length param' do 182 | truncate('This is line one. This is line two.', :length => 30, :break_token => '').should == 'This is line one. This is...' 183 | end 184 | it 'truncates before the length param if the break_token is before the token at "length"' do 185 | truncate('This is line one. This is line two.', :length => 30, :break_token => '').should == 'This is line one.' 186 | end 187 | end 188 | 189 | context 'when the break_token option is customized as a word' do 190 | it 'does not truncate abnormally if the break_token is not present' do 191 | truncate('This is line one. This is line two.', :length => 30, :break_token => 'foobar').should == 'This is line one. This is...' 192 | end 193 | it 'does not truncate abnormally if the break_token is present, but beyond the length param' do 194 | truncate('This is line one. This is line foobar two.', :length => 30, :break_token => 'foobar').should == 'This is line one. This is...' 195 | end 196 | it 'truncates before the length param if the break_token is before the token at "length"' do 197 | truncate('This is line one. foobar This is line two.', :length => 30, :break_token => 'foobar').should == 'This is line one.' 198 | end 199 | end 200 | 201 | context 'a string with comments' do 202 | it 'does not duplicate comments (issue #32)' do 203 | truncate('

hello and goodbye

', length: 15).should == 204 | '

hello and ...

' 205 | end 206 | end 207 | end 208 | -------------------------------------------------------------------------------- /spec/truncate_html/truncate_html_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), '..', 'spec_helper') 2 | 3 | describe TruncateHtml do 4 | it "includes itself in ActionController::Base" do 5 | ActionController::Base.should_receive(:helper).with(TruncateHtmlHelper) 6 | load File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'lib', 'truncate_html.rb')) 7 | end 8 | 9 | it "does not error if ActionController is undefined" do 10 | hide_const("ActionController") 11 | expect { 12 | load File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'lib', 'truncate_html.rb')) 13 | }.not_to raise_error 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /truncate_html.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.expand_path("../lib/truncate_html/version", __FILE__) 3 | 4 | Gem::Specification.new do |s| 5 | s.name = "truncate_html" 6 | s.version = TruncateHtml::VERSION 7 | s.authors = ["Harold Giménez"] 8 | s.email = ["harold.gimenez@gmail.com"] 9 | s.homepage = "https://github.com/hgmnz/truncate_html" 10 | s.summary = %q{Uses an API similar to Rails' truncate helper to truncate HTML and close any lingering open tags.} 11 | s.description = %q{Truncates html so you don't have to} 12 | 13 | s.files = `git ls-files`.split("\n") 14 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 15 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 16 | s.require_paths = ["lib"] 17 | 18 | s.required_ruby_version = '>= 1.9' 19 | 20 | s.add_development_dependency "rspec-rails", "~> 2.13" 21 | s.add_development_dependency "rails", "~> 3.2.13" 22 | end 23 | --------------------------------------------------------------------------------