1. Technology

Implementing StartsText With SubText As Pattern

When Pattern is a Regular Expressions!

By

Implementing StartsText With SubText As Pattern

Delphi RegExp for StartsText

Delphi RegExp for StartsText

Here's a task I needed to (programmatically, of course) solve recently: how to determine if a file (full path) is located "below" some predefined folder\subfolder(s) structure?

For a relative file path like "\abc\def\file.txt" I need to check if the file is located in the "\abc\def" folder (or in any of its sub folders). Let's call this "\abc\def" a predefined path. Note that I am not interested in the drive nor the path complexity before "\abc\def".

The StrUtils.pas, you can include in your unit uses list, exposes the StartsText function. StartsText determines if the substring begins a string using a case insensitive algorithm.

For the path and file above example,


  StartsText('\abc\def','\abc\def\file.txt') ... TRUE !
  StartsText('\abc\def','\abc\def\ghi\file.txt') ... TRUE !

The second part of the task was to get the remaining path of the file after the "predefined" one.

That's also easy: Copy('\abc\def\ghi\file.txt', 1 + Length('\abc\def'), MaxInt) would result in "\ghi\file.txt'".

Ok, so problem solved :) The StartsText function does the trick! Note that StartsText is case insensitive, if I wanted paths to be compared taking into account case sensitivity I would use StartsStr or even the Pos function.

As the case always is: the task got more complicated...

The task / problem to be solved was, of course, expanded into:

The "predefined" path could include some more folders where the naming of the folders was not known.

Something like: "abc\*\def".

The "*" here indicates a folder name.

For an example, a file : "abc\sf\def\file.txt" is OK, "abc\sf\def\ghi\file.txt" is OK, BUT "abc\sf\mf\def\ghi\file.txt" is NOT.

This means that in the above "predefined pattern" the "*" character means any single sub folder.

Could this be solved using StartsText? No! :(

Regular Expressions To The Rescue

Starting with Delphi XE, working with regular expressions is something Delphi supports by default. The "RegularExpressions" unit defines several record types exposing function and procedures you can do to "do regular expressions with Delphi".

Now, using regular expressions the task is: does "abc\sf\def\ghi\file.txt" match a pattern "abc\*\def" by looking at the beginning of the string?

If it does, what part of the value is a "match" and what part of the file name comes after the "predefined pattern"?

Lucky for me, Delphi has a solution. The Match function of the TRegEx record can be used to matches an input string (my file location) to the regular expression (my "predefined pattern"). The result of the Match function is a TMatch record type. The TMatch then exposes properties like "Success", "Length" and "Value".

Now, since we are up into regular expressions I need to write a regular expression for my "predefined pattern".

For my pattern "\abc\*\def\" a regular expression looks like "^\\abc\\[^\\]+\\def".

In regular expressions, a "\" (backslash) is a special character. To match it you use "\\" - meaning for every path delimiter two backslash characters. Next, my "*" means any folder name but NOT more than one level - i.e. a "\" is not allowed in "*". The regular expression token for this is "[^\\]+" - match one or more (+) of [^\\] - NOT "\". The starting "^" means "match the start of the string" - like the StartsText function does.

So, finally, a test application where "edMask" is a TEdit where you put your mask / pattern. edInputValue is a TEdit is where you put test values. memoMatch is here to "print" results.


var
  regMatch : TMatch;
  regPattern : string;
  relative : string;
begin
  memoMatch.Clear;

  regPattern := '^'+StringReplace(edMask.Text,'*','[^\]+',[rfReplaceAll]);
  regPattern := StringReplace(regPattern,'\','\\',[rfReplaceAll]);

  memoMatch.Lines.Add(Format('pattern: %s', [regPattern]));

  regMatch := TRegEx.Match(edInputValue.Text,regPattern, [roSingleLine, roIgnoreCase]);
  if regMatch.Success then 
  begin
    relative := Copy(edInputValue.Text, 1 + regMatch.Length, MAXINT);

    memoMatch.Lines.Add('Match!');
    memoMatch.Lines.Add(Format('"mask": %s', [edMask.Text]));
    memoMatch.Lines.Add(Format('match: %s', [regMatch.Value]));
    memoMatch.Lines.Add(Format('"relative": %s', [relative]));
  end
  else
  begin
    memoMatch.Lines.Add('NO match');
  end;
end;

And you still think playing with regular expressions is not fun? :))

If you would like to be a regular expressions guru: Online Regular Expression "Calculator"

Delphi tips navigator:
» Set Of Strings in Delphi - Mimic The Functionality of "set of string"
« Simple File Encryption/Decryption in Delphi

©2014 About.com. All rights reserved.