tags:

views:

112

answers:

2

I have a submenu that list departments. Behind this each department have an action who's name is assigned 'actPlan' + department.name.

Now I realize this was a bad idea because the name can contain any strange character in the world but the action.name cannot contain international characters. Obviously Delphi IDE itself call some method to validate if a string is a valid componentname. Anyone know more about this ?

I have also an idea to use

Action.name := 'actPlan' + department.departmentID;

instead. The advantage is that departmentID is a known format, 'xxxxx-x' (where x is 1-9), so I have only to replace '-' with for example underscore. The problem here is that those old actionnames are already persisted in a personal textfile. It will be exceptions if I suddenly change from using departments name to the ID.

I could of course eat the exception first time and then call a method that search replace that textfile with the right data and reload it.

So basically I search the most elegant and futureproof method to solve this :) I use D2007.

+2  A: 

I have written a routine

// See SysUtils.IsValidIdent:
function MakeValidIdent(const AText: string): string;
const
  Alpha = ['A'..'Z', 'a'..'z', '_'];
  AlphaNumeric = Alpha + ['0'..'9'];

  function IsValidChar(AIndex: Integer; AChar: Char): Boolean;
  begin
    if AIndex = 1 then
      Result := AChar in Alpha
    else
      Result := AChar in AlphaNumeric;
  end;

var
  i: Integer;
begin
  Result := AText;
  for i := 1 to Length(Result) do
    if not IsValidChar(i, Result[i]) then
      Result[i] := '_';
end;

which makes Pascal identifiers from strings. You might also want to copy FindUniqueName from Classes.pas and apply that to the result from MakeValidIdent.

Ulrich Gerhardt
Your routine doesn't work correctly, it accepts digits for the first character.
mghie
Thanks, I never noticed that. That's no problem if you use the routine as I do and berocoder could. So I have to decide whether to rename it or adjust the implementation.
Ulrich Gerhardt
Renaming it will not make it create valid component names - try it in the Object Inspector, "42" for example isn't a valid name.
mghie
But renaming it *would* remove the expectation that it is *supposed* to create valid component names. Its current name suggests that's what it does.
Rob Kennedy
After thinking about it, I'll correct the implementation (see my edit) and call "MakeValidIdent(APrefix + SomeRandomString)" instead of the current "APrefix + MakeValidIdent(SomeRandomString)". It's a tiny bit more inefficient for my use cases (where APrefix always is a valid identifier) but also fool proof.
Ulrich Gerhardt
@Rob: Exactly. :-)
Ulrich Gerhardt
@Rob: Renaming it would be fine - if it weren't for the question, which specifically asked for strings to be assigned to the component name. I'd expect an answer to the question to honour this.
mghie
@Ulrich: Your edited routine is much too baroque and unclear. Just test the first char against `Alpha`, start the loop from 2 instead of from 1, and test against `AlphaNumeric` in the loop - then you can remove `IsValidChar()`.
mghie
+3  A: 

Component names are validated using the IsValidIdent function from SysUtils, which simply checks whether the first character is alphabetic or an underscore and whether all subsequent characters are alphanumeric or an underscore.

To create a string that fits those rules, simply remove any characters that don't qualify, and then add a qualifying character if the result starts with a number.

That transformation might yield the same result for similar names. If that's not something you want, then you can add something unique to the end of the string, such as a checksum computed from the input string, or your department ID.

function MakeValidIdent(const s: string): string;
var
  len: Integer;
  x: Integer;
  c: Char;
begin
  SetLength(Result, Length(s));
  x := 0;
  for c in s do
    if c in ['A'..'Z', 'a'..'z', '0'..'9', '_'] then begin
      Inc(x);
      Result[x] := c;
    end;
  SetLength(Result, x);
  if x = 0 then
    Result := '_'
  else if Result[1] in ['0'..'9'] then
    Result := '_' + Result;
  // Optional uniqueness protection follows. Choose one.
  Result := Result + IntToStr(Checksum(s));
  Result := Result + GetDepartment(s).ID;
end;

In Delphi 2009 and later, replace the second two in operators with calls to the CharInSet function. (Unicode characters don't work well with Delphi sets.) In Delphi 8 and earlier, change the first in operator to a classic for loop and index into s.

Rob Kennedy