CChoiceFormat and truly PHP evaluation?

Hi, there!

I’m posting with two problems about CChoiceFormat.

Problem no 1. Sorry, because this is probably so obvious but I spent an hour to find a solution and failed! :[

I’ve noticed that CChoiceFormat::evaluate uses PHP’s eval() function (as I supposed). Why then I can’t use even simplest PHP codes in CChoiceFormat, like this one for example:


'strlen(n)==1#znak|n>=2 && n<5#znaki|n>=5#znakow'

If I’m not mistaken this should return ‘znak’ for number 1-9 as each of them has length = 1. Therefore I should get ‘7 znak’. But I don’t get it - it returns ‘7 znakow’.

Problem no 2. I’m dealing with above, because I’m trying to implement a Polish numbers ending (plural form) with CChoiceFormat. Maybe, aside of above, someone can help me with this or at least tell me, if it is possible at all?

In Polish language, an algorithm for getting proper plural ending is far more complicated than English simple "0 books / 1 book / 2 or more books’. It goes like this:

  • expression1 for 1 (the only case, singular form, as in English) - for example ‘1 znak’,
  • expression2 for any number, which last digit is 2-4, but not for 12-14, i.e. ‘2 znaki’, ‘24 znaki’, ‘194 723 znaki’ and so on,
  • expression3 for any number, which last digit is 0-1 or 5-9 and including 12-14, so: ‘0 znakow’, ‘5 znakow’, ‘12 znakow’, ‘19 znakow’, ‘1147 znakow’, 111 213 znakow’

It is so complicated that before Yii I had to write own function for this, with using number-to-string and string-to-number conversion. Here is this function in pseudo-code, basing on Delphi code as I haven’t got access right now to my PHP version of this function. It should return either 1, 2 or 3 depending on which expression I should add for passed number:


function polish_plural(AValue);

begin

  Result := '';

  iResult := 3; //default - most numbers fulfil this case


  //get last and last two digits for further comparision

  iLast := StrToInt(Copy(IntToStr(AValue), Length(IntToStr(AValue)), 1));

  iLastTwo := StrToInt(Copy(IntToStr(AValue), Length(IntToStr(AValue))-1, 2));


  if (iLast > 1) and (iLast < 5) then iResult := 2; //case two - numbers with 2-4 as last digit

  if (iLastTwo > 10) and (iLastTwo < 20) then iResult := 3; //force case three for numbers 11-19

  if AValue = 1 then iResult := 1;//the only case - singular form - number 1.


  return iResult;

end;

As I said, I have PHP version of above function. But I wonder if there would be any way to implement the same logic with CChoiceFormat to avoid using this function?

Any help would be much appreciate!

1st Problem

Try this: I think it is because of the && operator my friend. I have seen the format and try to enclose the expressions on brackets. Also, if you have numbers from 1-9 matching first expression, why you have the others >=2 and n<5? They will never be matched if expression one is executed.




'(strlen(n)==1)#znak|(n>=2 && n<5)#znaki|n>=5#znakow'



2nd Problem (challenging…)

n==1#znak|((substr(n,strlen(n)-1)>=2 || substr(n,strlen(n)-1)<=4) && (n != 12 && n != 14))#znaki|(substr(n,strlen(n)-1)<=1 || substr(n,strlen(n)-1)>=5) || n == 12 || n == 14)#znakow

You know? I think that all the sub-expressions should be converted to values and then use CChoiceFormat -never used though. For example:

n= number

ldigit = (substr(n,strlen(n)-1);

n==1#znak|(ldigit>=2 || ldigit <=4 && n != 12 && n!=14)#znaki|(ldigit<=1 || ldigit >=5 || n==12 || n==14)#znakow

edit: and then use the format function of CChoiceFormat to evaluate your functions

— see next post

Just an idea…

Hi Trej,

I was looking inside the code a bit more on CChoiceFormat and forget it my friend. Use that option for really simple conditional statements with numbers, do not use it with complicated ones.

I have tried with different options and they all giving me different results depending on the order. At the end I tried a simple IF ELSE and it seems to work, here is the code:




if( $n === 1) 

	echo 'znak';

else if((substr($n,strlen($n)-1)>=2 && substr($n,strlen($n)-1)<=4) && ($n != 12 && $n != 14))

	echo 'znaki';

else if (substr($n,strlen($n)-1)<=1 || substr($n,strlen($n)-1)>=5 || $n == 12 || $n == 14)

	echo 'znakow';



Write a function with it my friend if gives you the expected result.

Antonio, thank you for your wonderful support and spending your time on searching solution for me! :]

You answered yourself. First part of this expression was intentionally modified to test, why the whole thing isn’t working. In original concept, it was without strlen (only n==1). But if you look at a part of my post saying:

You’ll see that this is not working.

I just wanted someone who has a better regular expression experience than me (like you! :]) to confirm that doing this with regex or normal expression isn’t possible or would be to hard to code. You just did, for what I’m grateful to you. I’m using PHP 5.3.1 therefore I can make a use of unnamed functions. And - as you may see in my first post - I have one ready, I only need to change it slightly. Thanks!

Cheers Trej

edit: I think I will have a closer look to CChoiceFormat to see why that happened

update: looking inside code, forget about using streln <— last n is converted to $n for 'eval’uating expression

update:

Guess what Trej?

By modifying the inside code ‘n’ for ‘xyz’ in CChoiceFormat, you will be able to use the CChoiceFormat with complex expressions that involves PHP functions with ‘n’. So the following code is executed as expected:




echo CChoiceFormat::format('xyz==1#znak|((substr(xyz,strlen(xyz)-1)>=2 && substr(xyz,strlen(xyz)-1)<=4) && (xyz != 12 && xyz != 14))#znaki|(substr(xyz,strlen(xyz)-1)<1 || substr(xyz,strlen(xyz)-1)>=5) || xyz == 12 || xyz == 14)#znakow',1);

echo '<br/>';

echo CChoiceFormat::format('xyz==1#znak|((substr(xyz,strlen(xyz)-1)>=2 && substr(xyz,strlen(xyz)-1)<=4) && (xyz != 12 && xyz != 14))#znaki|(substr(xyz,strlen(xyz)-1)<1 || substr(xyz,strlen(xyz)-1)>=5) || xyz == 12 || xyz == 14)#znakow',3);

echo '<br/>';

echo CChoiceFormat::format('xyz==1#znak|((substr(xyz,strlen(xyz)-1)>=2 && substr(xyz,strlen(xyz)-1)<=4) && (xyz != 12 && xyz != 14))#znaki|(substr(xyz,strlen(xyz)-1)<1 || substr(xyz,strlen(xyz)-1)>=5) || xyz == 12 || xyz == 14)#znakow',12);

echo '<br/>';



The only issue is that you need to modify the inside code of CChoiceFormat:




class CChoiceFormat

{

	public static function format($messages, $number)

	{

		$n=preg_match_all('/\s*([^#]*)\s*#([^\|]*)\|/',$messages.'|',$matches);

		if($n===0)

			return $messages;

		for($i=0;$i<$n;++$i)

		{

			$expression=$matches[1][$i];	

			$message=$matches[2][$i];

			$intval=(int)$expression;

			

			if($expression==="$intval")

			{

				if($intval==$number)

					return $message;

			}

// WE HAVE CHANGED 'n' FOR 'xyz', NOW DO PROPER REPLACEMENT

			else if(self::evaluate(str_replace('xyz','$n',$expression),$number))

				return $message;

		}

		return $message; // return the last choice

	}


	protected static function evaluate($expression,$n)

	{

		return @eval("return $expression;");

	}

}



I think that by including a simple property to CChoiceFormat, one could specify which value should be replaced for the number ($n) and not be forced to use simple expressions to avoid wrong replacements.

Now I am satisfied