Skip to content

Commit

Permalink
Merge pull request #113 from ErikSchierboom/truncate-correct
Browse files Browse the repository at this point in the history
Added truncate methods. Implements #80
  • Loading branch information
MehdiK committed Mar 28, 2014
2 parents c991b2c + 3ebec56 commit c33227d
Show file tree
Hide file tree
Showing 11 changed files with 379 additions and 0 deletions.
29 changes: 29 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Also Humanizer [symbols nuget package](http://www.symbolsource.org/Public/Metada
- [Number to ordinal words](#number-to-ordinal-words)
- [Roman numerals](#roman-numerals)
- [ByteSize](#bytesize)
- [Truncate](#truncate)
- [Mix this into your framework to simplify your life](#mix-this-into-your-framework-to-simplify-your-life)
- [How to contribute?](#how-to-contribute)
- [Contribution guideline](#contribution-guideline)
Expand Down Expand Up @@ -514,6 +515,34 @@ ByteSize.Parse("1.55 tB");
ByteSize.Parse("1.55 tb");
```

###<a id="truncate">Truncate</a>
You can truncate a `string` using the `Truncate` method:

```c#
"Long text to truncate".Truncate(10) => "Long text…"
```

By default the `'…'` character is used to truncate strings. The advantage of using the `'…'` character instead of `"..."` is that the former only takes a single character and thus allows more text to be shown before truncation. If you want, you can also provide your own truncation string:

```c#
"Long text to truncate".Truncate(10, "---") => "Long te---"
```

The default truncation strategy, `Truncator.FixedLength`, is to truncate the input string to a specific length, including the truncation string length. There are two more truncator strategies available: one for a fixed number of (alpha-numerical) characters and one for a fixed number of words. To use a specific truncator when truncating, the two `Truncate` methods shown in the previous examples both have an overload that allow you to specify the `ITruncator` instance to use for the truncation. Here are examples on how to use the three provided truncators:

```c#
"Long text to truncate".Truncate(10, Truncator.FixedLength) => "Long text…"
"Long text to truncate".Truncate(10, "---", Truncator.FixedLength) => "Long te---"

"Long text to truncate".Truncate(6, Truncator.FixedNumberOfCharacters) => "Long t…"
"Long text to truncate".Truncate(6, "---", Truncator.FixedNumberOfCharacters) => "Lon---"

"Long text to truncate".Truncate(2, Truncator.FixedNumberOfWords) => "Long text…"
"Long text to truncate".Truncate(2, "---", Truncator.FixedNumberOfWords) => "Long text---"
```

Note that you can also use create your own truncator by having a class implement the `ITruncator` interface.

###<a id="mix-this-into-your-framework-to-simplify-your-life">Mix this into your framework to simplify your life</a>
This is just a baseline and you can use this to simplify your day to day job. For example, in Asp.Net MVC we keep chucking `Display` attribute on ViewModel properties so `HtmlHelper` can generate correct labels for us; but, just like enums, in vast majority of cases we just need a space between the words in property name - so why not use `"string".Humanize` for that?!

Expand Down
2 changes: 2 additions & 0 deletions release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

[Commits](https://github.com/MehdiK/Humanizer/compare/v1.14.1...master)

- [#110](https://github.com/MehdiK/Humanizer/pull/110): Added `Truncate` feature

###v1.14.1 - 2014-03-26
- [#108](https://github.com/MehdiK/Humanizer/pull/108): Added support for custom description attributes
- [#106](https://github.com/MehdiK/Humanizer/pull/106):
Expand Down
1 change: 1 addition & 0 deletions src/Humanizer.Tests/Humanizer.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ApiApprover\PublicApiApprovalTest.cs" />
<Compile Include="ApiApprover\PublicApiGenerator.cs" />
<Compile Include="TruncatorTests.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="Localisation\DateHumanizeTests.fi-FI.cs" />
Expand Down
116 changes: 116 additions & 0 deletions src/Humanizer.Tests/TruncatorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
using Xunit;
using Xunit.Extensions;

namespace Humanizer.Tests
{
public class TruncatorTests
{
[Theory]
[InlineData(null, 10, null)]
[InlineData("", 10, "")]
[InlineData("a", 1, "a")]
[InlineData("Text longer than truncate length", 10, "Text long…")]
[InlineData("Text with length equal to truncate length", 41, "Text with length equal to truncate length")]
[InlineData("Text smaller than truncate length", 34, "Text smaller than truncate length")]
public void Truncate(string input, int length, string expectedOutput)
{
Assert.Equal(expectedOutput, input.Truncate(length));
}

[Theory]
[InlineData(null, 10, null)]
[InlineData("", 10, "")]
[InlineData("a", 1, "a")]
[InlineData("Text longer than truncate length", 10, "Text long…")]
[InlineData("Text with length equal to truncate length", 41, "Text with length equal to truncate length")]
[InlineData("Text smaller than truncate length", 34, "Text smaller than truncate length")]
public void TruncateWithFixedLengthTruncator(string input, int length, string expectedOutput)
{
Assert.Equal(expectedOutput, input.Truncate(length, Truncator.FixedLength));
}

[Theory]
[InlineData(null, 10, null)]
[InlineData("", 10, "")]
[InlineData("a", 1, "a")]
[InlineData("Text with more characters than truncate length", 10, "Text with m…")]
[InlineData("Text with number of characters equal to truncate length", 47, "Text with number of characters equal to truncate length")]
[InlineData("Text with less characters than truncate length", 41, "Text with less characters than truncate length")]
public void TruncateWithFixedNumberOfCharactersTruncator(string input, int length, string expectedOutput)
{
Assert.Equal(expectedOutput, input.Truncate(length, Truncator.FixedNumberOfCharacters));
}

[Theory]
[InlineData(null, 10, null)]
[InlineData("", 10, "")]
[InlineData("a", 1, "a")]
[InlineData("Text with more words than truncate length", 4, "Text with more words…")]
[InlineData("Text with number of words equal to truncate length", 9, "Text with number of words equal to truncate length")]
[InlineData("Text with less words than truncate length", 8, "Text with less words than truncate length")]
[InlineData("Words are\nsplit\rby\twhitespace", 4, "Words are\nsplit\rby…")]
public void TruncateWithFixedNumberOfWordsTruncator(string input, int length, string expectedOutput)
{
Assert.Equal(expectedOutput, input.Truncate(length, Truncator.FixedNumberOfWords));
}

[Theory]
[InlineData(null, 10, "...", null)]
[InlineData("", 10, "...", "")]
[InlineData("a", 1, "...", "a")]
[InlineData("Text longer than truncate length", 10, "...", "Text lo...")]
[InlineData("Text with length equal to truncate length", 41, "...", "Text with length equal to truncate length")]
[InlineData("Text smaller than truncate length", 34, "...", "Text smaller than truncate length")]
[InlineData("Text with delimiter length greater than truncate length truncates to fixed length without truncation string", 2, "...", "Te")]
[InlineData("Null truncation string truncates to truncate length without truncation string", 4, null, "Null")]
public void TruncateWithTruncationString(string input, int length, string truncationString, string expectedOutput)
{
Assert.Equal(expectedOutput, input.Truncate(length, truncationString));
}

[Theory]
[InlineData(null, 10, "...", null)]
[InlineData("", 10, "...", "")]
[InlineData("a", 1, "...", "a")]
[InlineData("Text longer than truncate length", 10, "...", "Text lo...")]
[InlineData("Text with different truncation string", 10, "---", "Text wi---")]
[InlineData("Text with length equal to truncate length", 41, "...", "Text with length equal to truncate length")]
[InlineData("Text smaller than truncate length", 34, "...", "Text smaller than truncate length")]
[InlineData("Text with delimiter length greater than truncate length truncates to fixed length without truncation string", 2, "...", "Te")]
[InlineData("Null truncation string truncates to truncate length without truncation string", 4, null, "Null")]
public void TruncateWithTruncationStringAndFixedLengthTruncator(string input, int length, string truncationString, string expectedOutput)
{
Assert.Equal(expectedOutput, input.Truncate(length, truncationString, Truncator.FixedLength));
}

[Theory]
[InlineData(null, 10, "...", null)]
[InlineData("", 10, "...", "")]
[InlineData("a", 1, "...", "a")]
[InlineData("Text with more characters than truncate length", 10, "...", "Text wit...")]
[InlineData("Text with different truncation string", 10, "---", "Text wit---")]
[InlineData("Text with number of characters equal to truncate length", 47, "...", "Text with number of characters equal to truncate length")]
[InlineData("Text with less characters than truncate length", 41, "...", "Text with less characters than truncate length")]
[InlineData("Text with delimiter length greater than truncate length truncates to fixed length without truncation string", 2, "...", "Te")]
[InlineData("Null truncation string truncates to truncate length without truncation string", 4, null, "Null")]
public void TruncateWithTruncationStringAndFixedNumberOfCharactersTruncator(string input, int length, string truncationString, string expectedOutput)
{
Assert.Equal(expectedOutput, input.Truncate(length, truncationString, Truncator.FixedNumberOfCharacters));
}

[Theory]
[InlineData(null, 10, "...", null)]
[InlineData("", 10, "...", "")]
[InlineData("a", 1, "...", "a")]
[InlineData("Text with more words than truncate length", 4, "...", "Text with more words...")]
[InlineData("Text with different truncation string", 4, "---", "Text with different truncation---")]
[InlineData("Text with number of words equal to truncate length", 9, "...", "Text with number of words equal to truncate length")]
[InlineData("Text with less words than truncate length", 8, "...", "Text with less words than truncate length")]
[InlineData("Words are\nsplit\rby\twhitespace", 4, "...", "Words are\nsplit\rby...")]
[InlineData("Null truncation string truncates to truncate length without truncation string", 4, null, "Null truncation string truncates")]
public void TruncateWithTruncationStringAndFixedNumberOfWordsTruncator(string input, int length, string truncationString, string expectedOutput)
{
Assert.Equal(expectedOutput, input.Truncate(length, truncationString, Truncator.FixedNumberOfWords));
}
}
}
5 changes: 5 additions & 0 deletions src/Humanizer/Humanizer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@
<Compile Include="Transformer\ToUpperCase.cs" />
<Compile Include="Transformer\ToLowerCase.cs" />
<Compile Include="Transformer\ToSentenceCase.cs" />
<Compile Include="Truncation\FixedLengthTruncator.cs" />
<Compile Include="Truncation\FixedNumberOfCharactersTruncator.cs" />
<Compile Include="Truncation\FixedNumberOfWordsTruncator.cs" />
<Compile Include="Truncation\ITruncator.cs" />
<Compile Include="Truncation\Truncator.cs" />
</ItemGroup>
<ItemGroup>
<None Include="FluentDate\In.SomeTimeFrom.tt">
Expand Down
1 change: 1 addition & 0 deletions src/Humanizer/Humanizer.csproj.DotSettings
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=FluentDate/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=Truncation/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=Resources/@EntryIndexedValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=Transformer/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
22 changes: 22 additions & 0 deletions src/Humanizer/Truncation/FixedLengthTruncator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Humanizer
{
/// <summary>
/// Truncate a string to a fixed length
/// </summary>
class FixedLengthTruncator : ITruncator
{
public string Truncate(string value, int length, string truncationString)
{
if (value == null)
return null;

if (value.Length == 0)
return value;

if (truncationString == null || truncationString.Length > length)
return value.Substring(0, length);

return value.Length > length ? value.Substring(0, length - truncationString.Length) + truncationString : value;
}
}
}
41 changes: 41 additions & 0 deletions src/Humanizer/Truncation/FixedNumberOfCharactersTruncator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System;
using System.Linq;

namespace Humanizer
{
/// <summary>
/// Truncate a string to a fixed number of characters
/// </summary>
class FixedNumberOfCharactersTruncator : ITruncator
{
public string Truncate(string value, int length, string truncationString)
{
if (value == null)
return null;

if (value.Length == 0)
return value;

if (truncationString == null || truncationString.Length > length)
return value.Substring(0, length);

var alphaNumericalCharactersProcessed = 0;

var numberOfCharactersEqualToTruncateLength = value.ToCharArray().Count(Char.IsLetterOrDigit) == length;

for (var i = 0; i < value.Length - truncationString.Length; i++)
{
if (Char.IsLetterOrDigit(value[i]))
alphaNumericalCharactersProcessed++;

if (numberOfCharactersEqualToTruncateLength && alphaNumericalCharactersProcessed == length)
return value;

if (!numberOfCharactersEqualToTruncateLength && alphaNumericalCharactersProcessed + truncationString.Length == length)
return value.Substring(0, i + 1) + truncationString;
}

return value;
}
}
}
48 changes: 48 additions & 0 deletions src/Humanizer/Truncation/FixedNumberOfWordsTruncator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;
using System.Linq;

namespace Humanizer
{
/// <summary>
/// Truncate a string to a fixed number of words
/// </summary>
class FixedNumberOfWordsTruncator : ITruncator
{
public string Truncate(string value, int length, string truncationString)
{
if (value == null)
return null;

if (value.Length == 0)
return value;

var numberOfWordsProcessed = 0;
var numberOfWords = value.Split((char[])null, StringSplitOptions.RemoveEmptyEntries).Count();

if (numberOfWords <= length)
return value;

var lastCharactersWasWhiteSpace = true;

for (var i = 0; i < value.Length; i++)
{
if (Char.IsWhiteSpace(value[i]))
{
if (!lastCharactersWasWhiteSpace)
numberOfWordsProcessed++;

lastCharactersWasWhiteSpace = true;

if (numberOfWordsProcessed == length)
return value.Substring(0, i) + truncationString;
}
else
{
lastCharactersWasWhiteSpace = false;
}
}

return value + truncationString;
}
}
}
17 changes: 17 additions & 0 deletions src/Humanizer/Truncation/ITruncator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace Humanizer
{
/// <summary>
/// Can truncate a string.
/// </summary>
public interface ITruncator
{
/// <summary>
/// Truncate a string
/// </summary>
/// <param name="value">The string to truncate</param>
/// <param name="length">The length to truncate to</param>
/// <param name="truncationString">The string used to truncate with</param>
/// <returns>The truncated string</returns>
string Truncate(string value, int length, string truncationString);
}
}
Loading

0 comments on commit c33227d

Please sign in to comment.