Search code examples
c#wpftextblock

Trim/Crop specified text from both sides


I need to crop and display specified keyword(with some words around) from possibly long text.

So imagine a text like:

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua

keyword:

iscing

My goal is to display in some TextBlock with very limited width something like:

consectetur adipiscing elit

or (better)

...consectetur adipiscing elit...

number of words around the keyword should be based on a space available.

We use the MVVM pattern. Does anybody have a working solution for that? Thanks a lot

EDIT: I do not want to highlight the keyword right now.

EDIT 2: For the sake of simplicity assume that we want to display only the first occurrence of the "iscing" keyword.

Edit 3: To get it clear again - imagine you have some large text in application (document) and other component of appplication like TextBlock displaying the searched keyword from large text with some words around so the user can get the context. The Textblock can be resized at runtime so it should display more words (based on the size). Keyword should ideally stay somewhere in the middle of the Textblock.


Solution

  • This function do what you ask:

    public string Truncate(string input, string match, int nbCharMaxBefore, int nbCharMaxAfter)
    {  
        var inputSplit = input.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries);
    
        var index = 0;
        while (index < inputSplit.Length)
        {
            if (inputSplit[index].Contains(match)) break;
    
            index++;
        }
    
        if (index == inputSplit.Length)
        {
            // No match
            return String.Empty;
        }
    
        // Adds all the words before the match as long as the sum of all the words is not greater than the specified limit
        var previousWords = new List<string>();
        var i = index - 1;
        while (i >= 0)
        {
            var previousWord = inputSplit[i];
    
            if (previousWord.Length + previousWords.Sum(w => w.Length) < nbCharMaxBefore)
            {
                previousWords.Insert(0, previousWord);
            }
            else
            {
                break;
            }
    
            i--;
        }
    
        // Adds all the words after the match as long as the sum of all the words is not greater than the specified limit
        var nextWords = new List<string>();
        i = index + 1;
        while (i < inputSplit.Length)
        {
            var nextWord = inputSplit[i].TrimEnd(',');
    
            if (nextWord.Length + nextWords.Sum(w => w.Length) < nbCharMaxAfter)
            {
                nextWords.Add(nextWord);
            }
            else
            {
                break;
            }
    
            i++;
        }
    
        var prev = String.Join(" ", previousWords);
        var next = String.Join(" ", nextWords);
    
        return $"...{prev} {inputSplit[index]} {next}...";
    }
    

    You can use it like that:

    var input = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua";
    var match = "iscing";
    var nbCharMaxBefore = 15;
    var nbCharMaxAfter = 5;
    
    var truncatedStr = Truncate(input, match, nbCharBefore, nbCharAfter); // ...consectetur adipiscing elit...
    

    Concerning the MVVM way, considering the property you are binding on is MyText, just bind on another property like that:

    public string MyTruncatedText
    {
        get { return Truncate(this.MyText, this.MyMatch, this.NbCharBefore, this.NbCharAfter); }
    }
    

    Finally, in the setter of MyText, you want to call NotifyPropertyChanges(nameof(MyTruncatedProperty)) or the equivalent in your MVVM toolkit.