sobolevwladimir / vexcel
将 Excel 公式解析为语法树
Requires
- php: >=8.1.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.13
- kubawerlos/php-cs-fixer-custom-fixers: ^3.13
- phpstan/phpstan: ^1.10
- phpunit/phpunit: ^10
README
描述
Excel 公式是一种伟大的发明,它允许用户在不依赖程序员的情况下进行计算。经常会有这种情况,在您的应用程序中需要这样的功能!这个库允许您使用类似于 Excel 公式的表达式,唯一的区别是,您可以使用自己的变量而不是单元格坐标。
演示
http://vexcel.vladimir-sobolev.ru
特性
- 类似于 Excel 的语法。
- 支持外部定义的变量。
- 数学运算顺序(例如:2+2*3 得到 8)。
- 支持函数(IF、NOT、ROUND 等)。
- 可以添加自己的函数。
- 将语法树转换为 JSON 并反向转换。
- 将语法树中的变量存储为标识符。
- 可以将语法树反向转换为公式。(如果变量名称可能随时间改变,这很有用)。
语法
字符串
以单引号或双引号开头和结尾。例如:"我的字符串",'我的字符串'
数字
数字有两种类型:整数和小数。小数用点表示。
整数示例:1200
小数示例:3.2
变量
变量名称必须以字母开头,可以包含字母、数字和下划线。例如:Вася,Моя_ПЕР
变量还可以包含任何符号,包括空格,如果变量名称用 '$' 或 '' 包围。例如:$Моя ПЕР$,*\Моя ПЕР*
函数
函数名称必须以字母开头,可以包含字母、数字和下划线。函数名称后跟一个圆括号,其中传递函数的参数,参数之间用冒号分隔。函数在关闭括号后结束。例如:МОЯ_ФУНКЦИЯ(ПЕР1; ПЕР2)
安装
composer require sobolevwladimir/vexcel
开始
连接
<?php ... use SobolevWladimir\Vexcel\Parser\Parser;
接下来,我们可以将我们的公式转换为抽象语法树(以下简称 AST)。
$parser = new Parser(); $ast = $parser->parse('3+3');// Получили синтаксическое дерево
要获取值,请调用 calculate() 函数
$answer = $ast->calculate(); // $answer = 6
为什么这里有这么多步骤?因为计算是在 AST 中进行的,您只需要解析一次公式,然后,在改变变量的过程中,计算新字段的价值(关于变量 下面)。例如
$parser = new Parser(); $ast = $parser->parse('ВАША_ПЕРЕМЕННАЯ+3');// Получили синтаксическое дерево foreach($repositorys as $repository) { $answer = $ast->calculate($repository); // ... you code }
在公式中使用变量
要使用变量,我们需要向系统解释变量的值。为此,创建一个实现 ValueRepository 接口的类。例如,让我们假设我们有变量,其名称对应于数字("ПЕРЕМЕН_ОДИН"=1, "ПЕРЕМЕН_ДВА"=2, "ПЕРЕМЕН_ТРИ"=3 等)。那么这个类的样子将是
use SobolevWladimir\Vexcel\Repository\ValueRepositoryInterface; class ValueRepositoryFake implements ValueRepositoryInterface { /** @var array<string, int> */ private array $variables = [ 'ПЕРЕМЕН_ОДИН' => 1, 'ПЕРЕМЕН_ДВА' => 2, 'ПЕРЕМЕН_ТРИ' => 3, 'ПЕРЕМЕН_ЧЕТЫРЕ' => 4, 'ПЕРЕМЕН_ПЯТЬ' => 5, 'ПЕРЕМЕН_ШЕСТЬ' => 6, ]; public function getValueByIdentifier(string $identificator): mixed { return $this->variables[$identificator]; } }
函数getValueByIdentifier接受变量标识符(默认标识符等于变量名称)。了解更多:存储公式在数据库中。
然后我们将这个类的实例传递给计算器
$parser = new Parser(); $ast = $parser->parse('ПЕРЕМЕН_ТРИ+3');// Получили синтаксическое дерево $repository = new ValueRepositoryFake();// Наш репозиторий $answer = $ast->calculate($repository); // $answer = 6
使用函数
$parser = new Parser(); $ast = $parser->parse('ЕСЛИ(НЕ(3<2);"ДА";"НЕТ")');// Получили синтаксическое дерево $answer = $ast->calculate(); // $answer = 'ДА'
目前默认实现了以下函数:'IF', 'NOT', 'ROUNDUP', 'ROUNDDOWN'。如果您需要实现自己的函数,则创建一个继承自FunctionBuilder的类并重写"build"方法
$parser = new Parser(functionBuilder: new YouFunctionBuilder()); $ast = $parser->parse('МОЯФУНЦИЯ(3<2)');// Получили синтаксическое дерево $answer = $ast->calculate();
存储公式在数据库中
如果您的公式中有变量名称可能随时间变化的变量(例如:您的变量存储在数据库中),则存储用户输入的公式而不是公式本身,并在其中存储这些变量的恒定标识符是有意义的。然后在将树转换回公式时,将根据标识符恢复变量名称。
为此
- 创建一个实现VariableRepositoryInterface接口的类,并将其传递给Parser()构造函数。
- 通过调用$parser->parse()将公式转换为AST。
- 将此树以json格式保存在数据库中(json_encode($ast))。
要将json转换回语法树,执行以下步骤
$ast = FormulaAST::fromJson(json_decode((string)$formulaJson, true));
然后可以将此树转换回公式
$encoder = new VexcelEncoder($youVariableRepository); $code = $ast->toCode($encoder);